├── backend
├── public
│ ├── favicon.ico
│ ├── robots.txt
│ ├── index.php
│ └── .htaccess
├── resources
│ ├── css
│ │ └── app.css
│ └── js
│ │ ├── app.js
│ │ ├── bootstrap.js
│ │ └── echo.js
├── database
│ ├── .gitignore
│ ├── seeders
│ │ └── DatabaseSeeder.php
│ ├── migrations
│ │ ├── 2024_03_20_163112_create_comments_table.php
│ │ ├── 0001_01_01_000001_create_cache_table.php
│ │ ├── 2024_03_15_123846_create_personal_access_tokens_table.php
│ │ ├── 2024_03_17_105321_create_posts_table.php
│ │ ├── 0001_01_01_000000_create_users_table.php
│ │ └── 0001_01_01_000002_create_jobs_table.php
│ └── factories
│ │ └── UserFactory.php
├── storage
│ ├── logs
│ │ └── .gitignore
│ ├── app
│ │ ├── public
│ │ │ └── .gitignore
│ │ └── .gitignore
│ └── framework
│ │ ├── sessions
│ │ └── .gitignore
│ │ ├── testing
│ │ └── .gitignore
│ │ ├── views
│ │ └── .gitignore
│ │ ├── cache
│ │ ├── data
│ │ │ └── .gitignore
│ │ └── .gitignore
│ │ └── .gitignore
├── bootstrap
│ ├── cache
│ │ └── .gitignore
│ ├── providers.php
│ └── app.php
├── tests
│ ├── Unit
│ │ └── ExampleTest.php
│ ├── Feature
│ │ └── ExampleTest.php
│ ├── TestCase.php
│ └── Pest.php
├── app
│ ├── Http
│ │ ├── Controllers
│ │ │ ├── Controller.php
│ │ │ └── API
│ │ │ │ ├── UserController.php
│ │ │ │ ├── PostController.php
│ │ │ │ ├── CommentController.php
│ │ │ │ └── AuthController.php
│ │ └── Requests
│ │ │ └── PostRequest.php
│ ├── Providers
│ │ └── AppServiceProvider.php
│ ├── Models
│ │ ├── Comment.php
│ │ ├── Post.php
│ │ └── User.php
│ └── Events
│ │ ├── TestEvent.php
│ │ ├── CommentIncrement.php
│ │ └── PostBroadCastEvent.php
├── routes
│ ├── web.php
│ ├── channels.php
│ ├── console.php
│ └── api.php
├── .gitattributes
├── package.json
├── vite.config.js
├── .gitignore
├── .editorconfig
├── artisan
├── config
│ ├── cors.php
│ ├── services.php
│ ├── filesystems.php
│ ├── reverb.php
│ ├── broadcasting.php
│ ├── sanctum.php
│ ├── mail.php
│ ├── cache.php
│ ├── queue.php
│ ├── auth.php
│ ├── app.php
│ ├── logging.php
│ ├── database.php
│ └── session.php
├── phpunit.xml
├── .env.example
├── composer.json
└── README.md
├── .DS_Store
├── frontend
├── public
│ ├── avatar.png
│ ├── favicon.ico
│ └── logo.svg
├── postcss.config.js
├── src
│ ├── middleware.ts
│ ├── lib
│ │ ├── env.ts
│ │ ├── axios.config.ts
│ │ ├── apiEndPoints.ts
│ │ ├── utils.ts
│ │ └── echo.config.ts
│ ├── app
│ │ ├── api
│ │ │ ├── auth
│ │ │ │ └── [...nextauth]
│ │ │ │ │ ├── route.ts
│ │ │ │ │ └── authOptions.ts
│ │ │ └── image-preview
│ │ │ │ └── route.ts
│ │ ├── not-found.tsx
│ │ ├── (daily_dev)
│ │ │ ├── page.tsx
│ │ │ └── layout.tsx
│ │ ├── layout.tsx
│ │ ├── login
│ │ │ └── page.tsx
│ │ └── globals.css
│ ├── components
│ │ ├── base
│ │ │ ├── Sidebar.tsx
│ │ │ ├── SearchInput.tsx
│ │ │ ├── MobileSidebar.tsx
│ │ │ ├── Navbar.tsx
│ │ │ ├── SidebarLinks.tsx
│ │ │ └── ProfileMenu.tsx
│ │ ├── common
│ │ │ └── UserAvatar.tsx
│ │ ├── comment
│ │ │ ├── CommentCard.tsx
│ │ │ └── AddComment.tsx
│ │ ├── ui
│ │ │ ├── label.tsx
│ │ │ ├── textarea.tsx
│ │ │ ├── input.tsx
│ │ │ ├── button.tsx
│ │ │ ├── tabs.tsx
│ │ │ ├── card.tsx
│ │ │ ├── dialog.tsx
│ │ │ ├── sheet.tsx
│ │ │ └── dropdown-menu.tsx
│ │ ├── post
│ │ │ ├── ShowPost.tsx
│ │ │ ├── PostCard.tsx
│ │ │ ├── Posts.tsx
│ │ │ └── AddPost.tsx
│ │ └── auth
│ │ │ ├── Login.tsx
│ │ │ └── Register.tsx
│ ├── providers
│ │ └── AuthProvider.tsx
│ └── dataFetch
│ │ └── postFetch.ts
├── .env.example
├── next.config.mjs
├── components.json
├── .gitignore
├── tsconfig.json
├── package.json
├── types.ts
├── README.md
└── tailwind.config.ts
└── README.md
/backend/public/favicon.ico:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/backend/resources/css/app.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/backend/database/.gitignore:
--------------------------------------------------------------------------------
1 | *.sqlite*
2 |
--------------------------------------------------------------------------------
/backend/resources/js/app.js:
--------------------------------------------------------------------------------
1 | import './bootstrap';
2 |
--------------------------------------------------------------------------------
/backend/storage/logs/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/backend/bootstrap/cache/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/backend/public/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Disallow:
3 |
--------------------------------------------------------------------------------
/backend/storage/app/public/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/backend/storage/app/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !public/
3 | !.gitignore
4 |
--------------------------------------------------------------------------------
/backend/storage/framework/sessions/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/backend/storage/framework/testing/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/backend/storage/framework/views/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/backend/storage/framework/cache/data/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/backend/storage/framework/cache/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !data/
3 | !.gitignore
4 |
--------------------------------------------------------------------------------
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TusharVashishth/daily-dev-clone/HEAD/.DS_Store
--------------------------------------------------------------------------------
/backend/bootstrap/providers.php:
--------------------------------------------------------------------------------
1 | toBeTrue();
5 | });
6 |
--------------------------------------------------------------------------------
/frontend/src/middleware.ts:
--------------------------------------------------------------------------------
1 | export { default } from "next-auth/middleware";
2 |
3 | export const config = { matcher: ["/", "/profile"] };
4 |
--------------------------------------------------------------------------------
/backend/app/Http/Controllers/Controller.php:
--------------------------------------------------------------------------------
1 | get('/');
5 |
6 | $response->assertStatus(200);
7 | });
8 |
--------------------------------------------------------------------------------
/backend/storage/framework/.gitignore:
--------------------------------------------------------------------------------
1 | compiled.php
2 | config.php
3 | down
4 | events.scanned.php
5 | maintenance.php
6 | routes.php
7 | routes.scanned.php
8 | schedule-*
9 | services.json
10 |
--------------------------------------------------------------------------------
/frontend/src/lib/env.ts:
--------------------------------------------------------------------------------
1 | export default class Env {
2 | static API_URL: string = process.env.NEXT_PUBLIC_API_URL as string;
3 | static APP_URL: string = process.env.NEXT_PUBLIC_NEXTAUTH_URL as string;
4 | }
5 |
--------------------------------------------------------------------------------
/backend/tests/TestCase.php:
--------------------------------------------------------------------------------
1 | id === (int) $id;
9 | // return true;
10 | });
11 |
--------------------------------------------------------------------------------
/backend/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto eol=lf
2 |
3 | *.blade.php diff=html
4 | *.css diff=css
5 | *.html diff=html
6 | *.md diff=markdown
7 | *.php diff=php
8 |
9 | /.github export-ignore
10 | CHANGELOG.md export-ignore
11 | .styleci.yml export-ignore
12 |
--------------------------------------------------------------------------------
/backend/routes/console.php:
--------------------------------------------------------------------------------
1 | comment(Inspiring::quote());
8 | })->purpose('Display an inspiring quote')->hourly();
9 |
--------------------------------------------------------------------------------
/frontend/src/lib/axios.config.ts:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import { API_URL } from "./apiEndPoints";
3 |
4 | const myAxios = axios.create({
5 | baseURL: API_URL,
6 | headers: {
7 | Accept: "application/json",
8 | },
9 | });
10 |
11 | export default myAxios;
12 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Daily Dev Clone
2 |
3 | Let's Build Daily Dev Clone using the future by using future-proof technology Laravel 11 as the backend and Next js 14 with typescript as the Front end.
4 |
5 | In this video, we will also explore Reverb for real-time updates.
6 |
7 | If you use this code then forget to give this star ⭐️⭐️
8 |
--------------------------------------------------------------------------------
/backend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "type": "module",
4 | "scripts": {
5 | "dev": "vite",
6 | "build": "vite build"
7 | },
8 | "devDependencies": {
9 | "axios": "^1.6.4",
10 | "laravel-vite-plugin": "^1.0",
11 | "vite": "^5.0"
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/backend/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite';
2 | import laravel from 'laravel-vite-plugin';
3 |
4 | export default defineConfig({
5 | plugins: [
6 | laravel({
7 | input: ['resources/css/app.css', 'resources/js/app.js'],
8 | refresh: true,
9 | }),
10 | ],
11 | });
12 |
--------------------------------------------------------------------------------
/frontend/src/components/base/Sidebar.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import React from "react";
4 |
5 | import SidebarLinks from "./SidebarLinks";
6 |
7 | export default function Sidebar() {
8 | return (
9 |
10 |
11 |
12 | );
13 | }
14 |
--------------------------------------------------------------------------------
/frontend/src/providers/AuthProvider.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import React from "react";
4 | import { SessionProvider } from "next-auth/react";
5 |
6 | export default function AuthProvider({
7 | children,
8 | }: {
9 | children: React.ReactNode;
10 | }) {
11 | return {children} ;
12 | }
13 |
--------------------------------------------------------------------------------
/backend/.gitignore:
--------------------------------------------------------------------------------
1 | /.phpunit.cache
2 | /node_modules
3 | /public/build
4 | /public/hot
5 | /public/storage
6 | /storage/*.key
7 | /vendor
8 | .env
9 | .env.backup
10 | .env.production
11 | .phpunit.result.cache
12 | Homestead.json
13 | Homestead.yaml
14 | auth.json
15 | npm-debug.log
16 | yarn-error.log
17 | /.fleet
18 | /.idea
19 | /.vscode
20 |
--------------------------------------------------------------------------------
/backend/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | end_of_line = lf
6 | indent_size = 4
7 | indent_style = space
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
13 |
14 | [*.{yml,yaml}]
15 | indent_size = 2
16 |
17 | [docker-compose.yml]
18 | indent_size = 4
19 |
--------------------------------------------------------------------------------
/frontend/.env.example:
--------------------------------------------------------------------------------
1 | NEXT_PUBLIC_NEXTAUTH_URL=http://localhost:3000
2 | NEXTAUTH_SECRET="YOUR_KEY"
3 | NEXT_PUBLIC_API_URL=http://localhost:8000
4 |
5 | NEXT_PUBLIC_REVERB_APP_ID=266721
6 | NEXT_PUBLIC_REVERB_APP_KEY=YOUR_KEY
7 | NEXT_PUBLIC_REVERB_APP_SECRET=YOUR_KEY
8 | NEXT_PUBLIC_REVERB_HOST="localhost"
9 | NEXT_PUBLIC_REVERB_PORT=8080
10 | NEXT_PUBLIC_REVERB_SCHEME=http
--------------------------------------------------------------------------------
/frontend/next.config.mjs:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | images: {
4 | remotePatterns: [
5 | {
6 | hostname: "*",
7 | protocol: "https",
8 | },
9 | {
10 | hostname: "localhost",
11 | protocol: "http",
12 | },
13 | ],
14 | },
15 | reactStrictMode: false,
16 | };
17 |
18 | export default nextConfig;
19 |
--------------------------------------------------------------------------------
/backend/artisan:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 | handleCommand(new ArgvInput);
14 |
15 | exit($status);
16 |
--------------------------------------------------------------------------------
/frontend/components.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema.json",
3 | "style": "new-york",
4 | "rsc": true,
5 | "tsx": true,
6 | "tailwind": {
7 | "config": "tailwind.config.ts",
8 | "css": "src/app/globals.css",
9 | "baseColor": "slate",
10 | "cssVariables": true,
11 | "prefix": ""
12 | },
13 | "aliases": {
14 | "components": "@/components",
15 | "utils": "@/lib/utils"
16 | }
17 | }
--------------------------------------------------------------------------------
/backend/resources/js/bootstrap.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | window.axios = axios;
3 |
4 | window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
5 |
6 | /**
7 | * Echo exposes an expressive API for subscribing to channels and listening
8 | * for events that are broadcast by Laravel. Echo and event broadcasting
9 | * allow your team to quickly build robust real-time web applications.
10 | */
11 |
12 | import './echo';
13 |
--------------------------------------------------------------------------------
/frontend/src/lib/apiEndPoints.ts:
--------------------------------------------------------------------------------
1 | import Env from "./env";
2 |
3 | export const API_URL = Env.API_URL + "/api";
4 | export const LOGIN_URL = "/auth/login";
5 | export const REGISTER_URL = "/auth/register";
6 | export const CHECK_CREDENTIALS = "/auth/checkCredentials";
7 | export const LOGOUT_URL = "/auth/logout";
8 |
9 | export const POST_URL = "/post";
10 | export const COMMENT_URL = "/comment";
11 | export const UPDATE_PROFILE = "/update/profile";
12 |
--------------------------------------------------------------------------------
/frontend/src/components/common/UserAvatar.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Image from "next/image";
3 | import { getImageUrl } from "@/lib/utils";
4 | export default function UserAvatar({ image }: { image?: string }) {
5 | return (
6 |
7 | {image ? (
8 |
9 | ) : (
10 |
11 | )}
12 |
13 | );
14 | }
15 |
--------------------------------------------------------------------------------
/backend/app/Providers/AppServiceProvider.php:
--------------------------------------------------------------------------------
1 |
6 |
10 |
11 |
12 |
13 | );
14 | }
15 |
--------------------------------------------------------------------------------
/backend/resources/js/echo.js:
--------------------------------------------------------------------------------
1 | import Echo from 'laravel-echo';
2 |
3 | import Pusher from 'pusher-js';
4 | window.Pusher = Pusher;
5 |
6 | window.Echo = new Echo({
7 | broadcaster: 'reverb',
8 | key: import.meta.env.VITE_REVERB_APP_KEY,
9 | wsHost: import.meta.env.VITE_REVERB_HOST,
10 | wsPort: import.meta.env.VITE_REVERB_PORT ?? 80,
11 | wssPort: import.meta.env.VITE_REVERB_PORT ?? 443,
12 | forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? 'https') === 'https',
13 | enabledTransports: ['ws', 'wss'],
14 | });
15 |
--------------------------------------------------------------------------------
/backend/public/index.php:
--------------------------------------------------------------------------------
1 | handleRequest(Request::capture());
18 |
--------------------------------------------------------------------------------
/backend/app/Models/Comment.php:
--------------------------------------------------------------------------------
1 | belongsTo(User::class, "user_id", "id")->select("id", "profile_image", "username", "name");
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/frontend/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 | .yarn/install-state.gz
8 |
9 | # testing
10 | /coverage
11 |
12 | # next.js
13 | /.next/
14 | /out/
15 |
16 | # production
17 | /build
18 |
19 | # misc
20 | .DS_Store
21 | *.pem
22 |
23 | # debug
24 | npm-debug.log*
25 | yarn-debug.log*
26 | yarn-error.log*
27 |
28 | # local env files
29 | .env*.local
30 |
31 | # vercel
32 | .vercel
33 |
34 | # typescript
35 | *.tsbuildinfo
36 | next-env.d.ts
37 |
--------------------------------------------------------------------------------
/frontend/src/app/not-found.tsx:
--------------------------------------------------------------------------------
1 | import { Button } from "@/components/ui/button";
2 | import Image from "next/image";
3 | import Link from "next/link";
4 |
5 | export default function NotFound() {
6 | return (
7 |
8 |
15 |
16 |
17 | Back to Home
18 |
19 |
20 | );
21 | }
22 |
--------------------------------------------------------------------------------
/frontend/src/dataFetch/postFetch.ts:
--------------------------------------------------------------------------------
1 | import { API_URL, POST_URL } from "@/lib/apiEndPoints";
2 |
3 | export async function getPosts(token: string, nextPage?: string) {
4 | const res = await fetch(API_URL + POST_URL, {
5 | headers: {
6 | Authorization: `Bearer ${token}`,
7 | },
8 | });
9 | // The return value is *not* serialized
10 | // You can return Date, Map, Set, etc.
11 |
12 | if (!res.ok) {
13 | // This will activate the closest `error.js` Error Boundary
14 | throw new Error("Failed to fetch data");
15 | }
16 |
17 | return res.json();
18 | }
19 |
--------------------------------------------------------------------------------
/backend/app/Models/Post.php:
--------------------------------------------------------------------------------
1 | belongsTo(User::class, "user_id", "id")->select("id", "profile_image");
18 | }
19 |
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/backend/database/seeders/DatabaseSeeder.php:
--------------------------------------------------------------------------------
1 | create();
17 |
18 | User::factory()->create([
19 | 'name' => 'Test User',
20 | 'email' => 'test@example.com',
21 | ]);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/frontend/src/app/(daily_dev)/page.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { getServerSession } from "next-auth";
3 | import {
4 | CustomSession,
5 | authOptions,
6 | } from "../api/auth/[...nextauth]/authOptions";
7 | import { getPosts } from "@/dataFetch/postFetch";
8 | import Posts from "@/components/post/Posts";
9 |
10 | export default async function App() {
11 | const session = (await getServerSession(authOptions)) as CustomSession;
12 | const posts: APIResponseType = await getPosts(
13 | session.user?.token!
14 | );
15 | return (
16 |
19 | );
20 | }
21 |
--------------------------------------------------------------------------------
/frontend/src/app/api/image-preview/route.ts:
--------------------------------------------------------------------------------
1 | import { NextRequest, NextResponse } from "next/server";
2 | import { getLinkPreview } from "link-preview-js";
3 | import { unstable_noStore } from "next/cache";
4 |
5 | export async function POST(request: NextRequest) {
6 | unstable_noStore();
7 | try {
8 | const body = await request.json();
9 | const data: ImagePreviewResType = (await getLinkPreview(body.url, {
10 | imagesPropertyType: "og",
11 | followRedirects: "follow",
12 | })) as ImagePreviewResType;
13 | return NextResponse.json({ status: 200, data });
14 | } catch (error) {
15 | return NextResponse.json({ status: 404, message: "Not found" });
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/backend/public/.htaccess:
--------------------------------------------------------------------------------
1 |
2 |
3 | Options -MultiViews -Indexes
4 |
5 |
6 | RewriteEngine On
7 |
8 | # Handle Authorization Header
9 | RewriteCond %{HTTP:Authorization} .
10 | RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
11 |
12 | # Redirect Trailing Slashes If Not A Folder...
13 | RewriteCond %{REQUEST_FILENAME} !-d
14 | RewriteCond %{REQUEST_URI} (.+)/$
15 | RewriteRule ^ %1 [L,R=301]
16 |
17 | # Send Requests To Front Controller...
18 | RewriteCond %{REQUEST_FILENAME} !-d
19 | RewriteCond %{REQUEST_FILENAME} !-f
20 | RewriteRule ^ index.php [L]
21 |
22 |
--------------------------------------------------------------------------------
/frontend/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "lib": ["dom", "dom.iterable", "esnext"],
4 | "allowJs": true,
5 | "skipLibCheck": true,
6 | "strict": true,
7 | "noEmit": true,
8 | "esModuleInterop": true,
9 | "module": "esnext",
10 | "moduleResolution": "bundler",
11 | "resolveJsonModule": true,
12 | "isolatedModules": true,
13 | "jsx": "preserve",
14 | "incremental": true,
15 | "plugins": [
16 | {
17 | "name": "next"
18 | }
19 | ],
20 | "paths": {
21 | "@/*": ["./src/*"]
22 | }
23 | },
24 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
25 | "exclude": ["node_modules"]
26 | }
27 |
--------------------------------------------------------------------------------
/backend/bootstrap/app.php:
--------------------------------------------------------------------------------
1 | withRouting(
9 | web: __DIR__.'/../routes/web.php',
10 | api: __DIR__.'/../routes/api.php',
11 | commands: __DIR__.'/../routes/console.php',
12 | channels: __DIR__.'/../routes/channels.php',
13 | health: '/up',
14 | )
15 | ->withMiddleware(function (Middleware $middleware) {
16 | //
17 | })
18 | ->withExceptions(function (Exceptions $exceptions) {
19 | //
20 | })->create();
21 |
--------------------------------------------------------------------------------
/frontend/src/components/base/MobileSidebar.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import React, { useState } from "react";
3 | import {
4 | Sheet,
5 | SheetContent,
6 | SheetDescription,
7 | SheetHeader,
8 | SheetTitle,
9 | SheetTrigger,
10 | } from "@/components/ui/sheet";
11 | import { Menu } from "lucide-react";
12 | import SidebarLinks from "./SidebarLinks";
13 |
14 | export default function MobileSidebar() {
15 | const [open, setOpen] = useState(false);
16 | return (
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | );
26 | }
27 |
--------------------------------------------------------------------------------
/frontend/src/components/comment/CommentCard.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import UserAvatar from "../common/UserAvatar";
3 | import { formatDate } from "@/lib/utils";
4 |
5 | export default function CommentCard({ comment }: { comment: CommentType }) {
6 | return (
7 |
8 |
9 |
10 |
11 |
{comment.user.name}
12 |
13 | @{comment.user.username} . {formatDate(comment.created_at)}
14 |
15 |
16 |
17 |
{comment.comment}
18 |
19 | );
20 | }
21 |
--------------------------------------------------------------------------------
/frontend/src/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { type ClassValue, clsx } from "clsx";
2 | import { twMerge } from "tailwind-merge";
3 | import moment from "moment";
4 | import Env from "./env";
5 |
6 | export function cn(...inputs: ClassValue[]) {
7 | return twMerge(clsx(inputs));
8 | }
9 |
10 | export const isValidUrl = (url: string): boolean => {
11 | try {
12 | new URL(url);
13 | return true;
14 | } catch (error) {
15 | return false;
16 | }
17 | };
18 |
19 | export const trimString = (data: string) => {
20 | if (data.length <= 50) {
21 | return data;
22 | }
23 | return data.slice(0, 50) + "...";
24 | };
25 |
26 | export const formatDate = (date: string) => {
27 | return moment(date).format("DD MMM YYYY");
28 | };
29 |
30 | export const getImageUrl = (path: string): string => {
31 | return `${Env.API_URL}/storage/${path}`;
32 | };
33 |
--------------------------------------------------------------------------------
/frontend/src/app/(daily_dev)/layout.tsx:
--------------------------------------------------------------------------------
1 | import Navbar from "@/components/base/Navbar";
2 | import Sidebar from "@/components/base/Sidebar";
3 | import { getServerSession } from "next-auth";
4 | import {
5 | CustomSession,
6 | authOptions,
7 | } from "../api/auth/[...nextauth]/authOptions";
8 |
9 | export default async function DailyDevLayout({
10 | children,
11 | }: Readonly<{
12 | children: React.ReactNode;
13 | }>) {
14 | const session = (await getServerSession(authOptions)) as CustomSession;
15 | return (
16 |
17 |
18 |
19 |
20 |
21 | {children}
22 |
23 |
24 |
25 | );
26 | }
27 |
--------------------------------------------------------------------------------
/frontend/src/components/ui/label.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as LabelPrimitive from "@radix-ui/react-label"
5 | import { cva, type VariantProps } from "class-variance-authority"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const labelVariants = cva(
10 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
11 | )
12 |
13 | const Label = React.forwardRef<
14 | React.ElementRef,
15 | React.ComponentPropsWithoutRef &
16 | VariantProps
17 | >(({ className, ...props }, ref) => (
18 |
23 | ))
24 | Label.displayName = LabelPrimitive.Root.displayName
25 |
26 | export { Label }
27 |
--------------------------------------------------------------------------------
/frontend/src/components/ui/textarea.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | export interface TextareaProps
6 | extends React.TextareaHTMLAttributes {}
7 |
8 | const Textarea = React.forwardRef(
9 | ({ className, ...props }, ref) => {
10 | return (
11 |
19 | )
20 | }
21 | )
22 | Textarea.displayName = "Textarea"
23 |
24 | export { Textarea }
25 |
--------------------------------------------------------------------------------
/backend/app/Http/Requests/PostRequest.php:
--------------------------------------------------------------------------------
1 | |string>
21 | */
22 | public function rules(): array
23 | {
24 | return [
25 | "title" => "required|min:2|max:190",
26 | "url" => "required|url:https",
27 | "image_url" => "required|url:https,http",
28 | "description" => "nullable|min:10|max:20000"
29 | ];
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/backend/database/migrations/2024_03_20_163112_create_comments_table.php:
--------------------------------------------------------------------------------
1 | id();
15 | $table->foreignId("user_id")->constrained()->onDelete("CASCADE");
16 | $table->foreignId("post_id")->constrained()->onDelete("CASCADE");
17 | $table->text("comment")->nullable();
18 | $table->timestamps();
19 | });
20 | }
21 |
22 | /**
23 | * Reverse the migrations.
24 | */
25 | public function down(): void
26 | {
27 | Schema::dropIfExists('comments');
28 | }
29 | };
30 |
--------------------------------------------------------------------------------
/frontend/src/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import type { Metadata } from "next";
2 | import { Nunito } from "next/font/google";
3 | import "./globals.css";
4 | import { ToastContainer } from "react-toastify";
5 | import "react-toastify/dist/ReactToastify.css";
6 | import AuthProvider from "@/providers/AuthProvider";
7 |
8 | const inter = Nunito({ subsets: ["latin"] });
9 |
10 | export const metadata: Metadata = {
11 | title: "daily.dev | Where developers suffere together",
12 | description: "daily.dev | Where developers suffere together",
13 | };
14 |
15 | export default function RootLayout({
16 | children,
17 | }: Readonly<{
18 | children: React.ReactNode;
19 | }>) {
20 | return (
21 |
22 |
23 |
24 | {children}
25 |
26 |
27 | );
28 | }
29 |
--------------------------------------------------------------------------------
/frontend/src/components/ui/input.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | export interface InputProps
6 | extends React.InputHTMLAttributes {}
7 |
8 | const Input = React.forwardRef(
9 | ({ className, type, ...props }, ref) => {
10 | return (
11 |
20 | )
21 | }
22 | )
23 | Input.displayName = "Input"
24 |
25 | export { Input }
26 |
--------------------------------------------------------------------------------
/backend/config/cors.php:
--------------------------------------------------------------------------------
1 | ['api/*', 'sanctum/csrf-cookie', "/broadcasting/auth"],
19 |
20 | 'allowed_methods' => ['*'],
21 |
22 | 'allowed_origins' => ['*'],
23 |
24 | 'allowed_origins_patterns' => [],
25 |
26 | 'allowed_headers' => ['*'],
27 |
28 | 'exposed_headers' => [],
29 |
30 | 'max_age' => 0,
31 |
32 | 'supports_credentials' => false,
33 |
34 | ];
35 |
--------------------------------------------------------------------------------
/frontend/src/components/base/Navbar.tsx:
--------------------------------------------------------------------------------
1 | import Image from "next/image";
2 | import React from "react";
3 | import SearchInput from "./SearchInput";
4 | import { BellIcon } from "@radix-ui/react-icons";
5 | import { Button } from "../ui/button";
6 | import { Menu } from "lucide-react";
7 | import MobileSidebar from "./MobileSidebar";
8 | import ProfileMenu from "./ProfileMenu";
9 | import { CustomUser } from "@/app/api/auth/[...nextauth]/authOptions";
10 |
11 | export default function Navbar({ user }: { user: CustomUser }) {
12 | return (
13 |
14 |
15 |
16 |
17 |
18 |
24 |
25 | );
26 | }
27 |
--------------------------------------------------------------------------------
/backend/database/migrations/0001_01_01_000001_create_cache_table.php:
--------------------------------------------------------------------------------
1 | string('key')->primary();
16 | $table->mediumText('value');
17 | $table->integer('expiration');
18 | });
19 |
20 | Schema::create('cache_locks', function (Blueprint $table) {
21 | $table->string('key')->primary();
22 | $table->string('owner');
23 | $table->integer('expiration');
24 | });
25 | }
26 |
27 | /**
28 | * Reverse the migrations.
29 | */
30 | public function down(): void
31 | {
32 | Schema::dropIfExists('cache');
33 | Schema::dropIfExists('cache_locks');
34 | }
35 | };
36 |
--------------------------------------------------------------------------------
/backend/app/Http/Controllers/API/UserController.php:
--------------------------------------------------------------------------------
1 | validate([
15 | "profile_image" => "required|image|mimes:jpg,png,svg,webp,jpeg,gif|max:2048"
16 | ]);
17 | try {
18 | $user = $request->user();
19 | $filename = $payload["profile_image"]->store("images_" . $user->id);
20 | User::where("id", $user->id)->update(["profile_image" => $filename]);
21 | return response()->json(["image" => $filename]);
22 | } catch (\Exception $err) {
23 | Log::info("Profile image error =>" . $err->getMessage());
24 | return response()->json(["message" => "Something went wrong!"], 500);
25 | }
26 |
27 |
28 |
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/backend/database/migrations/2024_03_15_123846_create_personal_access_tokens_table.php:
--------------------------------------------------------------------------------
1 | id();
16 | $table->morphs('tokenable');
17 | $table->string('name');
18 | $table->string('token', 64)->unique();
19 | $table->text('abilities')->nullable();
20 | $table->timestamp('last_used_at')->nullable();
21 | $table->timestamp('expires_at')->nullable();
22 | $table->timestamps();
23 | });
24 | }
25 |
26 | /**
27 | * Reverse the migrations.
28 | */
29 | public function down(): void
30 | {
31 | Schema::dropIfExists('personal_access_tokens');
32 | }
33 | };
34 |
--------------------------------------------------------------------------------
/backend/database/migrations/2024_03_17_105321_create_posts_table.php:
--------------------------------------------------------------------------------
1 | id();
15 | $table->foreignId('user_id')->constrained()->onDelete("CASCADE");
16 | $table->text("url");
17 | $table->text("image_url");
18 | $table->string("title")->index();
19 | $table->text("description")->nullable();
20 | $table->unsignedInteger("vote")->default(0);
21 | $table->unsignedInteger("comment_count")->default(0);
22 | $table->timestamps();
23 | });
24 | }
25 |
26 | /**
27 | * Reverse the migrations.
28 | */
29 | public function down(): void
30 | {
31 | Schema::dropIfExists('posts');
32 | }
33 | };
34 |
--------------------------------------------------------------------------------
/backend/config/services.php:
--------------------------------------------------------------------------------
1 | [
18 | 'token' => env('POSTMARK_TOKEN'),
19 | ],
20 |
21 | 'ses' => [
22 | 'key' => env('AWS_ACCESS_KEY_ID'),
23 | 'secret' => env('AWS_SECRET_ACCESS_KEY'),
24 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
25 | ],
26 |
27 | 'slack' => [
28 | 'notifications' => [
29 | 'bot_user_oauth_token' => env('SLACK_BOT_USER_OAUTH_TOKEN'),
30 | 'channel' => env('SLACK_BOT_USER_DEFAULT_CHANNEL'),
31 | ],
32 | ],
33 |
34 | ];
35 |
--------------------------------------------------------------------------------
/backend/app/Events/TestEvent.php:
--------------------------------------------------------------------------------
1 | data = $payload;
25 | }
26 |
27 | /**
28 | * Get the channels the event should broadcast on.
29 | *
30 | * @return array
31 | */
32 | public function broadcastOn(): array
33 | {
34 | return
35 | [
36 | new Channel('test-event'),
37 | ];
38 | // return new Channel('test-channel');
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/backend/app/Events/CommentIncrement.php:
--------------------------------------------------------------------------------
1 | post_id = $post_id;
25 | }
26 |
27 | /**
28 | * Get the channels the event should broadcast on.
29 | *
30 | * @return array
31 | */
32 | public function broadcastOn(): array
33 | {
34 | return [
35 | new Channel('post-broadcast'),
36 | ];
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/backend/app/Events/PostBroadCastEvent.php:
--------------------------------------------------------------------------------
1 | post = $post;
26 | }
27 |
28 | /**
29 | * Get the channels the event should broadcast on.
30 | *
31 | * @return array
32 | */
33 | public function broadcastOn(): array
34 | {
35 | return [
36 | new Channel('post-broadcast'),
37 | ];
38 | }
39 |
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/backend/app/Models/User.php:
--------------------------------------------------------------------------------
1 |
19 | */
20 | protected $fillable = [
21 | 'name',
22 | 'email',
23 | 'password',
24 | 'username',
25 | 'profile_image'
26 | ];
27 |
28 | /**
29 | * The attributes that should be hidden for serialization.
30 | *
31 | * @var array
32 | */
33 | protected $hidden = [
34 | 'password',
35 | 'remember_token',
36 | ];
37 |
38 | /**
39 | * Get the attributes that should be cast.
40 | *
41 | * @return array
42 | */
43 | protected function casts(): array
44 | {
45 | return [
46 | 'email_verified_at' => 'datetime',
47 | 'password' => 'hashed',
48 | ];
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/backend/database/factories/UserFactory.php:
--------------------------------------------------------------------------------
1 |
11 | */
12 | class UserFactory extends Factory
13 | {
14 | /**
15 | * The current password being used by the factory.
16 | */
17 | protected static ?string $password;
18 |
19 | /**
20 | * Define the model's default state.
21 | *
22 | * @return array
23 | */
24 | public function definition(): array
25 | {
26 | return [
27 | 'name' => fake()->name(),
28 | 'email' => fake()->unique()->safeEmail(),
29 | 'email_verified_at' => now(),
30 | 'password' => static::$password ??= Hash::make('password'),
31 | 'remember_token' => Str::random(10),
32 | ];
33 | }
34 |
35 | /**
36 | * Indicate that the model's email address should be unverified.
37 | */
38 | public function unverified(): static
39 | {
40 | return $this->state(fn (array $attributes) => [
41 | 'email_verified_at' => null,
42 | ]);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/frontend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "frontend",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@radix-ui/react-dialog": "^1.0.5",
13 | "@radix-ui/react-dropdown-menu": "^2.0.6",
14 | "@radix-ui/react-icons": "^1.3.0",
15 | "@radix-ui/react-label": "^2.0.2",
16 | "@radix-ui/react-slot": "^1.0.2",
17 | "@radix-ui/react-tabs": "^1.0.4",
18 | "axios": "^1.6.8",
19 | "class-variance-authority": "^0.7.0",
20 | "clsx": "^2.1.0",
21 | "link-preview-js": "^3.0.5",
22 | "lucide-react": "^0.358.0",
23 | "moment": "^2.30.1",
24 | "next": "14.1.3",
25 | "next-auth": "^4.24.7",
26 | "react": "^18",
27 | "react-dom": "^18",
28 | "react-toastify": "^10.0.5",
29 | "tailwind-merge": "^2.2.1",
30 | "tailwindcss-animate": "^1.0.7",
31 | "use-immer": "^0.9.0"
32 | },
33 | "devDependencies": {
34 | "@types/node": "^20",
35 | "@types/react": "^18",
36 | "@types/react-dom": "^18",
37 | "autoprefixer": "^10.0.1",
38 | "laravel-echo": "^1.16.0",
39 | "postcss": "^8",
40 | "pusher-js": "^8.4.0-rc2",
41 | "tailwindcss": "^3.3.0",
42 | "typescript": "^5"
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/backend/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 | tests/Unit
10 |
11 |
12 | tests/Feature
13 |
14 |
15 |
16 |
17 | app
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/frontend/src/components/post/ShowPost.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import React, { useState } from "react";
3 | import {
4 | Dialog,
5 | DialogContent,
6 | DialogHeader,
7 | DialogTitle,
8 | DialogTrigger,
9 | } from "@/components/ui/dialog";
10 | import Image from "next/image";
11 | import AddComment from "../comment/AddComment";
12 |
13 | export default function ShowPost({
14 | children,
15 | post,
16 | }: {
17 | children: React.ReactNode;
18 | post: PostApiType;
19 | }) {
20 | const [open, setOpen] = useState(false);
21 | return (
22 |
23 |
24 | {children}
25 |
26 |
27 |
28 | Show Post
29 |
30 |
31 |
{post.title}
32 |
39 |
{post.description ?? ""}
40 |
41 |
42 |
43 |
44 |
45 | );
46 | }
47 |
--------------------------------------------------------------------------------
/backend/.env.example:
--------------------------------------------------------------------------------
1 | APP_NAME=Laravel
2 | APP_ENV=local
3 | APP_KEY=
4 | APP_DEBUG=true
5 | APP_TIMEZONE=UTC
6 | APP_URL=http://localhost
7 |
8 | APP_LOCALE=en
9 | APP_FALLBACK_LOCALE=en
10 | APP_FAKER_LOCALE=en_US
11 |
12 | APP_MAINTENANCE_DRIVER=file
13 | APP_MAINTENANCE_STORE=database
14 |
15 | BCRYPT_ROUNDS=12
16 |
17 | LOG_CHANNEL=stack
18 | LOG_STACK=single
19 | LOG_DEPRECATIONS_CHANNEL=null
20 | LOG_LEVEL=debug
21 |
22 | DB_CONNECTION=pgsql
23 | DB_HOST=127.0.0.1
24 | DB_PORT=5432
25 | DB_DATABASE=backend
26 | DB_USERNAME=root
27 | DB_PASSWORD=
28 |
29 | SESSION_DRIVER=database
30 | SESSION_LIFETIME=120
31 | SESSION_ENCRYPT=false
32 | SESSION_PATH=/
33 | SESSION_DOMAIN=null
34 |
35 | BROADCAST_CONNECTION=log
36 | FILESYSTEM_DISK=local
37 | QUEUE_CONNECTION=database
38 |
39 | CACHE_STORE=database
40 | CACHE_PREFIX=
41 |
42 | MEMCACHED_HOST=127.0.0.1
43 |
44 | REDIS_CLIENT=phpredis
45 | REDIS_HOST=127.0.0.1
46 | REDIS_PASSWORD=null
47 | REDIS_PORT=6379
48 |
49 | MAIL_MAILER=log
50 | MAIL_HOST=127.0.0.1
51 | MAIL_PORT=2525
52 | MAIL_USERNAME=null
53 | MAIL_PASSWORD=null
54 | MAIL_ENCRYPTION=null
55 | MAIL_FROM_ADDRESS="hello@example.com"
56 | MAIL_FROM_NAME="${APP_NAME}"
57 |
58 | AWS_ACCESS_KEY_ID=
59 | AWS_SECRET_ACCESS_KEY=
60 | AWS_DEFAULT_REGION=us-east-1
61 | AWS_BUCKET=
62 | AWS_USE_PATH_STYLE_ENDPOINT=false
63 |
64 | VITE_APP_NAME="${APP_NAME}"
65 |
--------------------------------------------------------------------------------
/frontend/src/lib/echo.config.ts:
--------------------------------------------------------------------------------
1 | import Echo from "laravel-echo";
2 | import Pusher from "pusher-js";
3 | import Env from "./env";
4 |
5 | // Define global types for Pusher and Echo (optional)
6 | declare global {
7 | interface Window {
8 | Pusher: typeof Pusher;
9 | Echo: Echo;
10 | }
11 | }
12 |
13 | window.Pusher = Pusher;
14 | export const pvtlaraEcho = (token: string): Echo => {
15 | return new Echo({
16 | broadcaster: "reverb",
17 | authEndpoint: Env.API_URL + "/api/broadcasting/auth",
18 | auth: {
19 | headers: {
20 | Authorization: "Bearer " + token,
21 | },
22 | },
23 | encrypted: false,
24 | key: process.env.NEXT_PUBLIC_REVERB_APP_KEY,
25 | wsHost: process.env.NEXT_PUBLIC_REVERB_HOST,
26 | wsPort: process.env.NEXT_PUBLIC_REVERB_PORT,
27 | wssPort: process.env.NEXT_PUBLIC_REVERB_PORT,
28 | forceTLS: (process.env.NEXT_PUBLIC_REVERB_SCHEME ?? "https") === "https",
29 | enabledTransports: ["ws", "wss"],
30 | });
31 | };
32 | export const laraEcho = new Echo({
33 | broadcaster: "reverb",
34 | encrypted: false,
35 | key: process.env.NEXT_PUBLIC_REVERB_APP_KEY,
36 | wsHost: process.env.NEXT_PUBLIC_REVERB_HOST,
37 | wsPort: process.env.NEXT_PUBLIC_REVERB_PORT,
38 | wssPort: process.env.NEXT_PUBLIC_REVERB_PORT,
39 | forceTLS: (process.env.NEXT_PUBLIC_REVERB_SCHEME ?? "https") === "https",
40 | enabledTransports: ["ws", "wss"],
41 | });
42 |
--------------------------------------------------------------------------------
/frontend/types.ts:
--------------------------------------------------------------------------------
1 | type AuthStateType = {
2 | email?: string;
3 | name?: string;
4 | password?: string;
5 | username?: string;
6 | password_confirmation?: string;
7 | };
8 |
9 | type PostStateType = {
10 | title?: string;
11 | url?: string;
12 | image_url?: string;
13 | description?: string;
14 | };
15 |
16 | type ImagePreviewResType = {
17 | url: string;
18 | title: string;
19 | siteName: string | undefined;
20 | description: string | undefined;
21 | mediaType: string;
22 | contentType: string | undefined;
23 | images: string[];
24 | videos: {};
25 | favicons: string[];
26 | };
27 |
28 | type APIResponseType = {
29 | data: Array;
30 | path: string;
31 | per_page: number;
32 | next_cursor: string;
33 | next_page_url?: string;
34 | prev_cursor?: string;
35 | prev_page_url?: string;
36 | };
37 |
38 | type PostApiType = {
39 | id: number;
40 | title: string;
41 | description?: string;
42 | image_url: string;
43 | url?: string;
44 | created_at: string;
45 | vote: number;
46 | comment_count: number;
47 | user_id: number;
48 | user: UserType;
49 | };
50 | type CommentType = {
51 | id: number;
52 | post_id: number;
53 | user_id: number;
54 | created_at: string;
55 | comment: string;
56 | user: UserType;
57 | };
58 |
59 | type UserType = {
60 | id: number;
61 | name: string;
62 | email: string;
63 | username: string;
64 | profile_image: string;
65 | };
66 |
--------------------------------------------------------------------------------
/backend/routes/api.php:
--------------------------------------------------------------------------------
1 | user();
16 | })->middleware('auth:sanctum');
17 |
18 | Route::middleware('auth:sanctum')->group(function () {
19 | Route::apiResources([
20 | "post" => PostController::class,
21 | "comment" => CommentController::class,
22 | ]);
23 |
24 | Route::post("/update/profile", [UserController::class, 'updateProfileImage']);
25 | });
26 |
27 |
28 | Route::post("/test/channel", function (Request $request) {
29 | // $post = Post::where("id", "2")->with('user')->first();
30 | // PostBroadCastEvent::dispatch($post);
31 | CommentIncrement::dispatch(2);
32 | // TestEvent::dispatch($request->all());
33 | return response()->json(["message" => "data sent successfully!"]);
34 | });
35 |
36 | Route::post("/auth/login", [AuthController::class, 'login']);
37 | Route::post("/auth/register", [AuthController::class, 'register']);
38 | Route::post("/auth/checkCredentials", [AuthController::class, 'checkCredentias']);
39 |
40 | Broadcast::routes(['middleware' => ['auth:sanctum']]);
--------------------------------------------------------------------------------
/frontend/src/components/base/SidebarLinks.tsx:
--------------------------------------------------------------------------------
1 | import Link from "next/link";
2 | import React from "react";
3 | import Image from "next/image";
4 | import { Search, Flame, ArrowBigUp, Link as LinkIcon } from "lucide-react";
5 | import AddPost from "../post/AddPost";
6 |
7 | export default function SidebarLinks() {
8 | return (
9 |
10 |
11 |
12 |
My feed
13 |
14 |
15 |
Discover
16 |
17 |
18 |
19 |
20 | Popular
21 |
22 |
23 |
24 |
25 |
26 |
27 | Search
28 |
29 |
30 |
31 |
32 |
33 | Most Voted
34 |
35 |
36 |
37 |
38 |
Contribute
39 |
44 |
45 | );
46 | }
47 |
--------------------------------------------------------------------------------
/frontend/src/app/login/page.tsx:
--------------------------------------------------------------------------------
1 | import Image from "next/image";
2 | import React from "react";
3 | import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
4 | import Login from "@/components/auth/Login";
5 | import Register from "@/components/auth/Register";
6 |
7 | export default function login() {
8 | return (
9 |
10 |
11 |
18 |
19 |
20 |
21 |
22 |
23 | Where developers suffer together
24 |
25 |
26 |
27 |
28 | Login
29 | Register
30 |
31 |
32 |
33 |
34 |
35 |
36 | );
37 | }
38 |
--------------------------------------------------------------------------------
/frontend/README.md:
--------------------------------------------------------------------------------
1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
2 |
3 | ## Getting Started
4 |
5 | First, run the development server:
6 |
7 | ```bash
8 | npm run dev
9 | # or
10 | yarn dev
11 | # or
12 | pnpm dev
13 | # or
14 | bun dev
15 | ```
16 |
17 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
18 |
19 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
20 |
21 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
22 |
23 | ## Learn More
24 |
25 | To learn more about Next.js, take a look at the following resources:
26 |
27 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
28 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
29 |
30 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
31 |
32 | ## Deploy on Vercel
33 |
34 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
35 |
36 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
37 |
--------------------------------------------------------------------------------
/backend/tests/Pest.php:
--------------------------------------------------------------------------------
1 | in('Feature');
18 |
19 | /*
20 | |--------------------------------------------------------------------------
21 | | Expectations
22 | |--------------------------------------------------------------------------
23 | |
24 | | When you're writing tests, you often need to check that values meet certain conditions. The
25 | | "expect()" function gives you access to a set of "expectations" methods that you can use
26 | | to assert different things. Of course, you may extend the Expectation API at any time.
27 | |
28 | */
29 |
30 | expect()->extend('toBeOne', function () {
31 | return $this->toBe(1);
32 | });
33 |
34 | /*
35 | |--------------------------------------------------------------------------
36 | | Functions
37 | |--------------------------------------------------------------------------
38 | |
39 | | While Pest is very powerful out-of-the-box, you may have some testing code specific to your
40 | | project that you don't want to repeat in every file. Here you can also expose helpers as
41 | | global functions to help you to reduce the number of lines of code in your test files.
42 | |
43 | */
44 |
45 | function something()
46 | {
47 | // ..
48 | }
49 |
--------------------------------------------------------------------------------
/backend/database/migrations/0001_01_01_000000_create_users_table.php:
--------------------------------------------------------------------------------
1 | id();
15 | $table->string('name');
16 | $table->string('email')->unique();
17 | $table->string('username')->unique();
18 | $table->string("profile_image")->nullable();
19 | $table->timestamp('email_verified_at')->nullable();
20 | $table->string('password');
21 | $table->rememberToken();
22 | $table->timestamps();
23 | });
24 |
25 | Schema::create('password_reset_tokens', function (Blueprint $table) {
26 | $table->string('email')->primary();
27 | $table->string('token');
28 | $table->timestamp('created_at')->nullable();
29 | });
30 |
31 | Schema::create('sessions', function (Blueprint $table) {
32 | $table->string('id')->primary();
33 | $table->foreignId('user_id')->nullable()->index();
34 | $table->string('ip_address', 45)->nullable();
35 | $table->text('user_agent')->nullable();
36 | $table->longText('payload');
37 | $table->integer('last_activity')->index();
38 | });
39 | }
40 |
41 | /**
42 | * Reverse the migrations.
43 | */
44 | public function down(): void
45 | {
46 | Schema::dropIfExists('users');
47 | Schema::dropIfExists('password_reset_tokens');
48 | Schema::dropIfExists('sessions');
49 | }
50 | };
51 |
--------------------------------------------------------------------------------
/frontend/src/components/post/PostCard.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {
3 | Card,
4 | CardContent,
5 | CardFooter,
6 | CardHeader,
7 | CardTitle,
8 | } from "@/components/ui/card";
9 | import Image from "next/image";
10 | import { ArrowBigUp, Link as LinkIcon, MessageSquare } from "lucide-react";
11 | import { formatDate, trimString } from "@/lib/utils";
12 | import { toast } from "react-toastify";
13 | import UserAvatar from "../common/UserAvatar";
14 |
15 | export default function PostCard({ post }: { post: PostApiType }) {
16 | const copyUrl = () => {
17 | navigator.clipboard.writeText(post.url!);
18 | toast.success("Link copied successfully!", { theme: "dark" });
19 | };
20 | return (
21 |
22 |
23 |
24 |
25 |
26 | {trimString(post.title)}
27 |
28 |
29 |
30 | {formatDate(post.created_at)}
31 |
32 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | {post.comment_count > 0 && {post.comment_count} }
46 |
47 |
48 |
49 |
50 |
51 |
52 | );
53 | }
54 |
--------------------------------------------------------------------------------
/frontend/src/app/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | @layer base {
6 | :root {
7 | --background: 0 0% 100%;
8 | --foreground: 222.2 84% 4.9%;
9 |
10 | --card: 0 0% 100%;
11 | --card-foreground: 222.2 84% 4.9%;
12 |
13 | --popover: 0 0% 100%;
14 | --popover-foreground: 222.2 84% 4.9%;
15 |
16 | --primary: 222.2 47.4% 11.2%;
17 | --primary-foreground: 210 40% 98%;
18 |
19 | --secondary: 210 40% 96.1%;
20 | --secondary-foreground: 222.2 47.4% 11.2%;
21 |
22 | --muted: 210 40% 96.1%;
23 | --muted-foreground: 215.4 16.3% 46.9%;
24 |
25 | --accent: 210 40% 96.1%;
26 | --accent-foreground: 222.2 47.4% 11.2%;
27 |
28 | --destructive: 0 84.2% 60.2%;
29 | --destructive-foreground: 210 40% 98%;
30 |
31 | --border: 214.3 31.8% 91.4%;
32 | --input: 214.3 31.8% 91.4%;
33 | --ring: 222.2 84% 4.9%;
34 |
35 | --radius: 0.5rem;
36 | }
37 |
38 | .dark {
39 |
40 | --background: 220, 23%, 8%;
41 | --foreground: 210 40% 98%;
42 |
43 | --card: 220, 23%, 8%;
44 | --card-foreground: 210 40% 98%;
45 |
46 | --popover: 220, 23%, 8%;
47 | --popover-foreground: 210 40% 98%;
48 |
49 | --primary: 210 40% 98%;
50 | --primary-foreground: 222.2 47.4% 11.2%;
51 |
52 | --secondary: 217.2 32.6% 17.5%;
53 | --secondary-foreground: 210 40% 98%;
54 |
55 | --muted: 225, 12%, 13%;
56 | --muted-foreground: 215 20.2% 65.1%;
57 |
58 | --accent: 217.2 32.6% 17.5%;
59 | --accent-foreground: 210 40% 98%;
60 |
61 | --destructive: 0 62.8% 30.6%;
62 | --destructive-foreground: 210 40% 98%;
63 |
64 | --border: 217.2 32.6% 17.5%;
65 | --input: 217.2 32.6% 17.5%;
66 | --ring: 212.7 26.8% 83.9%;
67 |
68 | --cabbage: #ce3df3
69 | }
70 | }
71 |
72 | @layer base {
73 | * {
74 | @apply border-border;
75 | }
76 |
77 | body {
78 | @apply bg-background text-foreground;
79 | }
80 | }
--------------------------------------------------------------------------------
/backend/app/Http/Controllers/API/PostController.php:
--------------------------------------------------------------------------------
1 | with('user')->orderByDesc("id")->cursorPaginate(20);
21 | return response()->json($posts);
22 | }
23 |
24 | /**
25 | * Store a newly created resource in storage.
26 | */
27 | public function store(PostRequest $request)
28 | {
29 | $payload = $request->validated();
30 | try {
31 | $user = $request->user();
32 | $payload["user_id"] = $user->id;
33 | $post = Post::create($payload)->with("user")->orderByDesc("id")->first();
34 | PostBroadCastEvent::dispatch($post);
35 | return response()->json(["message" => "Post created successfully!", "post" => $post]);
36 | } catch (\Exception $err) {
37 | Log::info("post-error => " . $err->getMessage());
38 | return response()->json(["message" => "something went wrong.please try again!"], 500);
39 | }
40 |
41 | }
42 |
43 | /**
44 | * Display the specified resource.
45 | */
46 | public function show(string $id)
47 | {
48 | //
49 | }
50 |
51 | /**
52 | * Update the specified resource in storage.
53 | */
54 | public function update(Request $request, string $id)
55 | {
56 | //
57 | }
58 |
59 | /**
60 | * Remove the specified resource from storage.
61 | */
62 | public function destroy(string $id)
63 | {
64 | //
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/frontend/src/components/ui/button.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { Slot } from "@radix-ui/react-slot"
3 | import { cva, type VariantProps } from "class-variance-authority"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const buttonVariants = cva(
8 | "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
9 | {
10 | variants: {
11 | variant: {
12 | default:
13 | "bg-primary text-primary-foreground shadow hover:bg-primary/90",
14 | destructive:
15 | "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
16 | outline:
17 | "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
18 | secondary:
19 | "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
20 | ghost: "hover:bg-accent hover:text-accent-foreground",
21 | link: "text-primary underline-offset-4 hover:underline",
22 | },
23 | size: {
24 | default: "h-9 px-4 py-2",
25 | sm: "h-8 rounded-md px-3 text-xs",
26 | lg: "h-10 rounded-md px-8",
27 | icon: "h-9 w-9",
28 | },
29 | },
30 | defaultVariants: {
31 | variant: "default",
32 | size: "default",
33 | },
34 | }
35 | )
36 |
37 | export interface ButtonProps
38 | extends React.ButtonHTMLAttributes,
39 | VariantProps {
40 | asChild?: boolean
41 | }
42 |
43 | const Button = React.forwardRef(
44 | ({ className, variant, size, asChild = false, ...props }, ref) => {
45 | const Comp = asChild ? Slot : "button"
46 | return (
47 |
52 | )
53 | }
54 | )
55 | Button.displayName = "Button"
56 |
57 | export { Button, buttonVariants }
58 |
--------------------------------------------------------------------------------
/backend/database/migrations/0001_01_01_000002_create_jobs_table.php:
--------------------------------------------------------------------------------
1 | id();
16 | $table->string('queue')->index();
17 | $table->longText('payload');
18 | $table->unsignedTinyInteger('attempts');
19 | $table->unsignedInteger('reserved_at')->nullable();
20 | $table->unsignedInteger('available_at');
21 | $table->unsignedInteger('created_at');
22 | });
23 |
24 | Schema::create('job_batches', function (Blueprint $table) {
25 | $table->string('id')->primary();
26 | $table->string('name');
27 | $table->integer('total_jobs');
28 | $table->integer('pending_jobs');
29 | $table->integer('failed_jobs');
30 | $table->longText('failed_job_ids');
31 | $table->mediumText('options')->nullable();
32 | $table->integer('cancelled_at')->nullable();
33 | $table->integer('created_at');
34 | $table->integer('finished_at')->nullable();
35 | });
36 |
37 | Schema::create('failed_jobs', function (Blueprint $table) {
38 | $table->id();
39 | $table->string('uuid')->unique();
40 | $table->text('connection');
41 | $table->text('queue');
42 | $table->longText('payload');
43 | $table->longText('exception');
44 | $table->timestamp('failed_at')->useCurrent();
45 | });
46 | }
47 |
48 | /**
49 | * Reverse the migrations.
50 | */
51 | public function down(): void
52 | {
53 | Schema::dropIfExists('jobs');
54 | Schema::dropIfExists('job_batches');
55 | Schema::dropIfExists('failed_jobs');
56 | }
57 | };
58 |
--------------------------------------------------------------------------------
/frontend/src/app/api/auth/[...nextauth]/authOptions.ts:
--------------------------------------------------------------------------------
1 | import { LOGIN_URL } from "@/lib/apiEndPoints";
2 | import myAxios from "@/lib/axios.config";
3 | import axios, { AxiosResponse } from "axios";
4 | import { AuthOptions, ISODateString, User } from "next-auth";
5 | import { JWT } from "next-auth/jwt";
6 | import CredentialsProvider from "next-auth/providers/credentials";
7 |
8 | export interface CustomSession {
9 | user?: CustomUser;
10 | expires: ISODateString;
11 | }
12 | export interface CustomUser {
13 | id?: string | null;
14 | name?: string | null;
15 | email?: string | null;
16 | profile_image?: string | null;
17 | token?: string | null;
18 | created_at?: string | null;
19 | updated_at?: string | null;
20 | }
21 | export const authOptions: AuthOptions = {
22 | pages: {
23 | signIn: "/login",
24 | },
25 | callbacks: {
26 | async jwt({ token, user, trigger, session }) {
27 | // * When we update the session
28 | if (trigger === "update" && session?.profile_image) {
29 | const user: CustomUser = token.user as CustomUser;
30 | user.profile_image = session?.profile_image;
31 | console.log("The token is", token);
32 | }
33 | if (user) {
34 | token.user = user;
35 | }
36 | return token;
37 | },
38 |
39 | async session({
40 | session,
41 | token,
42 | user,
43 | }: {
44 | session: CustomSession;
45 | token: JWT;
46 | user: User;
47 | }) {
48 | session.user = token.user as CustomUser;
49 | return session;
50 | },
51 | },
52 |
53 | providers: [
54 | CredentialsProvider({
55 | name: "Credentials",
56 | credentials: {
57 | email: {},
58 | password: {},
59 | },
60 | async authorize(credentials, req) {
61 | const res = await myAxios.post(LOGIN_URL, credentials);
62 | const response = res.data;
63 | const user = response?.user;
64 | if (user) {
65 | return user;
66 | } else {
67 | return null;
68 | }
69 | },
70 | }),
71 | ],
72 | };
73 |
--------------------------------------------------------------------------------
/frontend/src/components/ui/tabs.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as TabsPrimitive from "@radix-ui/react-tabs"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Tabs = TabsPrimitive.Root
9 |
10 | const TabsList = React.forwardRef<
11 | React.ElementRef,
12 | React.ComponentPropsWithoutRef
13 | >(({ className, ...props }, ref) => (
14 |
22 | ))
23 | TabsList.displayName = TabsPrimitive.List.displayName
24 |
25 | const TabsTrigger = React.forwardRef<
26 | React.ElementRef,
27 | React.ComponentPropsWithoutRef
28 | >(({ className, ...props }, ref) => (
29 |
37 | ))
38 | TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
39 |
40 | const TabsContent = React.forwardRef<
41 | React.ElementRef,
42 | React.ComponentPropsWithoutRef
43 | >(({ className, ...props }, ref) => (
44 |
52 | ))
53 | TabsContent.displayName = TabsPrimitive.Content.displayName
54 |
55 | export { Tabs, TabsList, TabsTrigger, TabsContent }
56 |
--------------------------------------------------------------------------------
/frontend/src/components/post/Posts.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import React, { useEffect } from "react";
3 | import PostCard from "./PostCard";
4 | import { useImmer } from "use-immer";
5 |
6 | import { CustomUser } from "@/app/api/auth/[...nextauth]/authOptions";
7 | import { toast } from "react-toastify";
8 | import ShowPost from "./ShowPost";
9 | import { laraEcho } from "@/lib/echo.config";
10 |
11 | export default function Posts({
12 | data,
13 | user,
14 | }: {
15 | data: APIResponseType;
16 | user: CustomUser;
17 | }) {
18 | const [posts, setPosts] = useImmer>(data);
19 |
20 | useEffect(() => {
21 | laraEcho
22 | .channel("post-broadcast")
23 | .listen("PostBroadCastEvent", (event: any) => {
24 | console.log("The event is", event?.post);
25 | const post: PostApiType = event.post;
26 | // Without immer
27 | // setPosts((prevPosts) => {
28 | // return {
29 | // ...prevPosts,
30 | // data: [post, ...prevPosts.data],
31 | // };
32 | // });
33 | setPosts((posts) => {
34 | posts.data = [post, ...posts.data];
35 | });
36 | toast.success("New Post added!!");
37 | })
38 | .listen("CommentIncrement", (event: any) => {
39 | setPosts((posts) => {
40 | const index = posts.data.findIndex(
41 | (item) => item.id === event.post_id
42 | );
43 | if (index !== -1) {
44 | posts.data[index].comment_count += 1;
45 | }
46 | });
47 | });
48 |
49 | // * Comment channel
50 |
51 | return () => {
52 | laraEcho.leave("post-broadcast");
53 | };
54 | }, []);
55 | return (
56 |
60 | {posts.data.length > 0 &&
61 | posts.data.map((item, index) => (
62 |
63 |
64 |
65 | ))}
66 |
67 | );
68 | }
69 |
--------------------------------------------------------------------------------
/frontend/src/components/ui/card.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | const Card = React.forwardRef<
6 | HTMLDivElement,
7 | React.HTMLAttributes
8 | >(({ className, ...props }, ref) => (
9 |
17 | ))
18 | Card.displayName = "Card"
19 |
20 | const CardHeader = React.forwardRef<
21 | HTMLDivElement,
22 | React.HTMLAttributes
23 | >(({ className, ...props }, ref) => (
24 |
29 | ))
30 | CardHeader.displayName = "CardHeader"
31 |
32 | const CardTitle = React.forwardRef<
33 | HTMLParagraphElement,
34 | React.HTMLAttributes
35 | >(({ className, ...props }, ref) => (
36 |
41 | ))
42 | CardTitle.displayName = "CardTitle"
43 |
44 | const CardDescription = React.forwardRef<
45 | HTMLParagraphElement,
46 | React.HTMLAttributes
47 | >(({ className, ...props }, ref) => (
48 |
53 | ))
54 | CardDescription.displayName = "CardDescription"
55 |
56 | const CardContent = React.forwardRef<
57 | HTMLDivElement,
58 | React.HTMLAttributes
59 | >(({ className, ...props }, ref) => (
60 |
61 | ))
62 | CardContent.displayName = "CardContent"
63 |
64 | const CardFooter = React.forwardRef<
65 | HTMLDivElement,
66 | React.HTMLAttributes
67 | >(({ className, ...props }, ref) => (
68 |
73 | ))
74 | CardFooter.displayName = "CardFooter"
75 |
76 | export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
77 |
--------------------------------------------------------------------------------
/backend/app/Http/Controllers/API/CommentController.php:
--------------------------------------------------------------------------------
1 | get("post_id");
20 | $comments = Comment::select("id", "user_id", "post_id", "comment", "created_at")->where("post_id", $postId)->with('user')->orderByDesc("id")->cursorPaginate(15);
21 | return response()->json($comments);
22 | }
23 |
24 | /**
25 | * Store a newly created resource in storage.
26 | */
27 | public function store(Request $request)
28 | {
29 | $payload = $request->validate([
30 | "comment" => "required|min:2|max:2000",
31 | "post_id" => "required"
32 | ]);
33 | try {
34 | $user = $request->user();
35 | $payload["user_id"] = $user->id;
36 | Post::where("id", $payload["post_id"])->increment("comment_count", 1);
37 | $comment = Comment::create($payload)->with('user')->orderByDesc("id")->first();
38 | CommentIncrement::dispatch($payload["post_id"]);
39 | return response()->json(["comment" => $comment, "message" => "Comment added succcesfully!"]);
40 | } catch (\Exception $err) {
41 | Log::info("post-error => " . $err->getMessage());
42 | return response()->json(["message" => "something went wrong.please try again!"], 500);
43 | }
44 |
45 | }
46 |
47 | /**
48 | * Display the specified resource.
49 | */
50 | public function show(string $id)
51 | {
52 | //
53 | }
54 |
55 | /**
56 | * Update the specified resource in storage.
57 | */
58 | public function update(Request $request, string $id)
59 | {
60 | //
61 | }
62 |
63 | /**
64 | * Remove the specified resource from storage.
65 | */
66 | public function destroy(string $id)
67 | {
68 | //
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/backend/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "laravel/laravel",
3 | "type": "project",
4 | "description": "The skeleton application for the Laravel framework.",
5 | "keywords": ["laravel", "framework"],
6 | "license": "MIT",
7 | "require": {
8 | "php": "^8.2",
9 | "laravel/framework": "^11.0",
10 | "laravel/reverb": "@beta",
11 | "laravel/sanctum": "^4.0",
12 | "laravel/tinker": "^2.9"
13 | },
14 | "require-dev": {
15 | "fakerphp/faker": "^1.23",
16 | "laravel/pint": "^1.13",
17 | "laravel/sail": "^1.26",
18 | "mockery/mockery": "^1.6",
19 | "nunomaduro/collision": "^8.0",
20 | "pestphp/pest": "^2.34",
21 | "pestphp/pest-plugin-laravel": "^2.3",
22 | "spatie/laravel-ignition": "^2.4"
23 | },
24 | "autoload": {
25 | "psr-4": {
26 | "App\\": "app/",
27 | "Database\\Factories\\": "database/factories/",
28 | "Database\\Seeders\\": "database/seeders/"
29 | }
30 | },
31 | "autoload-dev": {
32 | "psr-4": {
33 | "Tests\\": "tests/"
34 | }
35 | },
36 | "scripts": {
37 | "post-autoload-dump": [
38 | "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
39 | "@php artisan package:discover --ansi"
40 | ],
41 | "post-update-cmd": [
42 | "@php artisan vendor:publish --tag=laravel-assets --ansi --force"
43 | ],
44 | "post-root-package-install": [
45 | "@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
46 | ],
47 | "post-create-project-cmd": [
48 | "@php artisan key:generate --ansi",
49 | "@php -r \"file_exists('database/database.sqlite') || touch('database/database.sqlite');\"",
50 | "@php artisan migrate --graceful --ansi"
51 | ]
52 | },
53 | "extra": {
54 | "laravel": {
55 | "dont-discover": []
56 | }
57 | },
58 | "config": {
59 | "optimize-autoloader": true,
60 | "preferred-install": "dist",
61 | "sort-packages": true,
62 | "allow-plugins": {
63 | "pestphp/pest-plugin": true,
64 | "php-http/discovery": true
65 | }
66 | },
67 | "minimum-stability": "stable",
68 | "prefer-stable": true
69 | }
70 |
--------------------------------------------------------------------------------
/frontend/tailwind.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from "tailwindcss";
2 |
3 | const config = {
4 | darkMode: ["class"],
5 | content: [
6 | "./pages/**/*.{ts,tsx}",
7 | "./components/**/*.{ts,tsx}",
8 | "./app/**/*.{ts,tsx}",
9 | "./src/**/*.{ts,tsx}",
10 | ],
11 | prefix: "",
12 | theme: {
13 | container: {
14 | center: true,
15 | padding: "2rem",
16 | screens: {
17 | "2xl": "1400px",
18 | },
19 | },
20 | extend: {
21 | colors: {
22 | border: "hsl(var(--border))",
23 | input: "hsl(var(--input))",
24 | ring: "hsl(var(--ring))",
25 | background: "hsl(var(--background))",
26 | foreground: "hsl(var(--foreground))",
27 | primary: {
28 | DEFAULT: "hsl(var(--primary))",
29 | foreground: "hsl(var(--primary-foreground))",
30 | },
31 | secondary: {
32 | DEFAULT: "hsl(var(--secondary))",
33 | foreground: "hsl(var(--secondary-foreground))",
34 | },
35 | destructive: {
36 | DEFAULT: "hsl(var(--destructive))",
37 | foreground: "hsl(var(--destructive-foreground))",
38 | },
39 | muted: {
40 | DEFAULT: "hsl(var(--muted))",
41 | foreground: "hsl(var(--muted-foreground))",
42 | },
43 | accent: {
44 | DEFAULT: "hsl(var(--accent))",
45 | foreground: "hsl(var(--accent-foreground))",
46 | },
47 | popover: {
48 | DEFAULT: "hsl(var(--popover))",
49 | foreground: "hsl(var(--popover-foreground))",
50 | },
51 | card: {
52 | DEFAULT: "hsl(var(--card))",
53 | foreground: "hsl(var(--card-foreground))",
54 | },
55 | cabbage: "var(--cabbage)",
56 | },
57 | borderRadius: {
58 | lg: "var(--radius)",
59 | md: "calc(var(--radius) - 2px)",
60 | sm: "calc(var(--radius) - 4px)",
61 | },
62 | keyframes: {
63 | "accordion-down": {
64 | from: { height: "0" },
65 | to: { height: "var(--radix-accordion-content-height)" },
66 | },
67 | "accordion-up": {
68 | from: { height: "var(--radix-accordion-content-height)" },
69 | to: { height: "0" },
70 | },
71 | },
72 | animation: {
73 | "accordion-down": "accordion-down 0.2s ease-out",
74 | "accordion-up": "accordion-up 0.2s ease-out",
75 | },
76 | },
77 | },
78 | plugins: [require("tailwindcss-animate")],
79 | } satisfies Config;
80 |
81 | export default config;
82 |
--------------------------------------------------------------------------------
/backend/config/filesystems.php:
--------------------------------------------------------------------------------
1 | env('FILESYSTEM_DISK', 'local'),
17 |
18 | /*
19 | |--------------------------------------------------------------------------
20 | | Filesystem Disks
21 | |--------------------------------------------------------------------------
22 | |
23 | | Below you may configure as many filesystem disks as necessary, and you
24 | | may even configure multiple disks for the same driver. Examples for
25 | | most supported storage drivers are configured here for reference.
26 | |
27 | | Supported Drivers: "local", "ftp", "sftp", "s3"
28 | |
29 | */
30 |
31 | 'disks' => [
32 |
33 | 'local' => [
34 | 'driver' => 'local',
35 | 'root' => storage_path('app'),
36 | 'throw' => false,
37 | ],
38 |
39 | 'public' => [
40 | 'driver' => 'local',
41 | 'root' => storage_path('app/public'),
42 | 'url' => env('APP_URL').'/storage',
43 | 'visibility' => 'public',
44 | 'throw' => false,
45 | ],
46 |
47 | 's3' => [
48 | 'driver' => 's3',
49 | 'key' => env('AWS_ACCESS_KEY_ID'),
50 | 'secret' => env('AWS_SECRET_ACCESS_KEY'),
51 | 'region' => env('AWS_DEFAULT_REGION'),
52 | 'bucket' => env('AWS_BUCKET'),
53 | 'url' => env('AWS_URL'),
54 | 'endpoint' => env('AWS_ENDPOINT'),
55 | 'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false),
56 | 'throw' => false,
57 | ],
58 |
59 | ],
60 |
61 | /*
62 | |--------------------------------------------------------------------------
63 | | Symbolic Links
64 | |--------------------------------------------------------------------------
65 | |
66 | | Here you may configure the symbolic links that will be created when the
67 | | `storage:link` Artisan command is executed. The array keys should be
68 | | the locations of the links and the values should be their targets.
69 | |
70 | */
71 |
72 | 'links' => [
73 | public_path('storage') => storage_path('app/public'),
74 | ],
75 |
76 | ];
77 |
--------------------------------------------------------------------------------
/backend/config/reverb.php:
--------------------------------------------------------------------------------
1 | env('REVERB_SERVER', 'reverb'),
17 |
18 | /*
19 | |--------------------------------------------------------------------------
20 | | Reverb Servers
21 | |--------------------------------------------------------------------------
22 | |
23 | | Here you may define details for each of the supported Reverb servers.
24 | | Each server has its own configuration options that are defined in
25 | | the array below. You should ensure all the options are present.
26 | |
27 | */
28 |
29 | 'servers' => [
30 |
31 | 'reverb' => [
32 | 'host' => env('REVERB_SERVER_HOST', '0.0.0.0'),
33 | 'port' => env('REVERB_SERVER_PORT', 8080),
34 | 'hostname' => env('REVERB_HOST'),
35 | 'options' => [
36 | 'tls' => [],
37 | ],
38 | 'scaling' => [
39 | 'enabled' => env('REVERB_SCALING_ENABLED', false),
40 | 'channel' => env('REVERB_SCALING_CHANNEL', 'reverb'),
41 | ],
42 | 'pulse_ingest_interval' => env('REVERB_PULSE_INGEST_INTERVAL', 15),
43 | ],
44 |
45 | ],
46 |
47 | /*
48 | |--------------------------------------------------------------------------
49 | | Reverb Applications
50 | |--------------------------------------------------------------------------
51 | |
52 | | Here you may define how Reverb applications are managed. If you choose
53 | | to use the "config" provider, you may define an array of apps which
54 | | your server will support, including their connection credentials.
55 | |
56 | */
57 |
58 | 'apps' => [
59 |
60 | 'provider' => 'config',
61 |
62 | 'apps' => [
63 | [
64 | 'key' => env('REVERB_APP_KEY'),
65 | 'secret' => env('REVERB_APP_SECRET'),
66 | 'app_id' => env('REVERB_APP_ID'),
67 | 'options' => [
68 | 'host' => env('REVERB_HOST'),
69 | 'port' => env('REVERB_PORT', 443),
70 | 'scheme' => env('REVERB_SCHEME', 'https'),
71 | 'useTLS' => env('REVERB_SCHEME', 'https') === 'https',
72 | ],
73 | 'allowed_origins' => ['*'],
74 | 'ping_interval' => env('REVERB_APP_PING_INTERVAL', 60),
75 | 'max_message_size' => env('REVERB_APP_MAX_MESSAGE_SIZE', 10_000),
76 | ],
77 | ],
78 |
79 | ],
80 |
81 | ];
82 |
--------------------------------------------------------------------------------
/backend/config/broadcasting.php:
--------------------------------------------------------------------------------
1 | env('BROADCAST_CONNECTION', 'null'),
19 |
20 | /*
21 | |--------------------------------------------------------------------------
22 | | Broadcast Connections
23 | |--------------------------------------------------------------------------
24 | |
25 | | Here you may define all of the broadcast connections that will be used
26 | | to broadcast events to other systems or over WebSockets. Samples of
27 | | each available type of connection are provided inside this array.
28 | |
29 | */
30 |
31 | 'connections' => [
32 |
33 | 'reverb' => [
34 | 'driver' => 'reverb',
35 | 'key' => env('REVERB_APP_KEY'),
36 | 'secret' => env('REVERB_APP_SECRET'),
37 | 'app_id' => env('REVERB_APP_ID'),
38 | 'options' => [
39 | 'host' => env('REVERB_HOST'),
40 | 'port' => env('REVERB_PORT', 443),
41 | 'scheme' => env('REVERB_SCHEME', 'https'),
42 | 'useTLS' => env('REVERB_SCHEME', 'https') === 'https',
43 | ],
44 | 'client_options' => [
45 | // Guzzle client options: https://docs.guzzlephp.org/en/stable/request-options.html
46 | ],
47 | ],
48 |
49 | 'pusher' => [
50 | 'driver' => 'pusher',
51 | 'key' => env('PUSHER_APP_KEY'),
52 | 'secret' => env('PUSHER_APP_SECRET'),
53 | 'app_id' => env('PUSHER_APP_ID'),
54 | 'options' => [
55 | 'cluster' => env('PUSHER_APP_CLUSTER'),
56 | 'host' => env('PUSHER_HOST') ?: 'api-'.env('PUSHER_APP_CLUSTER', 'mt1').'.pusher.com',
57 | 'port' => env('PUSHER_PORT', 443),
58 | 'scheme' => env('PUSHER_SCHEME', 'https'),
59 | 'encrypted' => true,
60 | 'useTLS' => env('PUSHER_SCHEME', 'https') === 'https',
61 | ],
62 | 'client_options' => [
63 | // Guzzle client options: https://docs.guzzlephp.org/en/stable/request-options.html
64 | ],
65 | ],
66 |
67 | 'ably' => [
68 | 'driver' => 'ably',
69 | 'key' => env('ABLY_KEY'),
70 | ],
71 |
72 | 'log' => [
73 | 'driver' => 'log',
74 | ],
75 |
76 | 'null' => [
77 | 'driver' => 'null',
78 | ],
79 |
80 | ],
81 |
82 | ];
83 |
--------------------------------------------------------------------------------
/backend/config/sanctum.php:
--------------------------------------------------------------------------------
1 | explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf(
19 | '%s%s',
20 | 'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1',
21 | Sanctum::currentApplicationUrlWithPort()
22 | ))),
23 |
24 | /*
25 | |--------------------------------------------------------------------------
26 | | Sanctum Guards
27 | |--------------------------------------------------------------------------
28 | |
29 | | This array contains the authentication guards that will be checked when
30 | | Sanctum is trying to authenticate a request. If none of these guards
31 | | are able to authenticate the request, Sanctum will use the bearer
32 | | token that's present on an incoming request for authentication.
33 | |
34 | */
35 |
36 | 'guard' => ['web'],
37 |
38 | /*
39 | |--------------------------------------------------------------------------
40 | | Expiration Minutes
41 | |--------------------------------------------------------------------------
42 | |
43 | | This value controls the number of minutes until an issued token will be
44 | | considered expired. This will override any values set in the token's
45 | | "expires_at" attribute, but first-party sessions are not affected.
46 | |
47 | */
48 |
49 | 'expiration' => null,
50 |
51 | /*
52 | |--------------------------------------------------------------------------
53 | | Token Prefix
54 | |--------------------------------------------------------------------------
55 | |
56 | | Sanctum can prefix new tokens in order to take advantage of numerous
57 | | security scanning initiatives maintained by open source platforms
58 | | that notify developers if they commit tokens into repositories.
59 | |
60 | | See: https://docs.github.com/en/code-security/secret-scanning/about-secret-scanning
61 | |
62 | */
63 |
64 | 'token_prefix' => env('SANCTUM_TOKEN_PREFIX', ''),
65 |
66 | /*
67 | |--------------------------------------------------------------------------
68 | | Sanctum Middleware
69 | |--------------------------------------------------------------------------
70 | |
71 | | When authenticating your first-party SPA with Sanctum you may need to
72 | | customize some of the middleware Sanctum uses while processing the
73 | | request. You may change the middleware listed below as required.
74 | |
75 | */
76 |
77 | 'middleware' => [
78 | 'authenticate_session' => Laravel\Sanctum\Http\Middleware\AuthenticateSession::class,
79 | 'encrypt_cookies' => Illuminate\Cookie\Middleware\EncryptCookies::class,
80 | 'validate_csrf_token' => Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class,
81 | ],
82 |
83 | ];
84 |
--------------------------------------------------------------------------------
/backend/config/mail.php:
--------------------------------------------------------------------------------
1 | env('MAIL_MAILER', 'log'),
18 |
19 | /*
20 | |--------------------------------------------------------------------------
21 | | Mailer Configurations
22 | |--------------------------------------------------------------------------
23 | |
24 | | Here you may configure all of the mailers used by your application plus
25 | | their respective settings. Several examples have been configured for
26 | | you and you are free to add your own as your application requires.
27 | |
28 | | Laravel supports a variety of mail "transport" drivers that can be used
29 | | when delivering an email. You may specify which one you're using for
30 | | your mailers below. You may also add additional mailers if needed.
31 | |
32 | | Supported: "smtp", "sendmail", "mailgun", "ses", "ses-v2",
33 | | "postmark", "log", "array", "failover", "roundrobin"
34 | |
35 | */
36 |
37 | 'mailers' => [
38 |
39 | 'smtp' => [
40 | 'transport' => 'smtp',
41 | 'url' => env('MAIL_URL'),
42 | 'host' => env('MAIL_HOST', '127.0.0.1'),
43 | 'port' => env('MAIL_PORT', 2525),
44 | 'encryption' => env('MAIL_ENCRYPTION', 'tls'),
45 | 'username' => env('MAIL_USERNAME'),
46 | 'password' => env('MAIL_PASSWORD'),
47 | 'timeout' => null,
48 | 'local_domain' => env('MAIL_EHLO_DOMAIN'),
49 | ],
50 |
51 | 'ses' => [
52 | 'transport' => 'ses',
53 | ],
54 |
55 | 'postmark' => [
56 | 'transport' => 'postmark',
57 | // 'message_stream_id' => env('POSTMARK_MESSAGE_STREAM_ID'),
58 | // 'client' => [
59 | // 'timeout' => 5,
60 | // ],
61 | ],
62 |
63 | 'sendmail' => [
64 | 'transport' => 'sendmail',
65 | 'path' => env('MAIL_SENDMAIL_PATH', '/usr/sbin/sendmail -bs -i'),
66 | ],
67 |
68 | 'log' => [
69 | 'transport' => 'log',
70 | 'channel' => env('MAIL_LOG_CHANNEL'),
71 | ],
72 |
73 | 'array' => [
74 | 'transport' => 'array',
75 | ],
76 |
77 | 'failover' => [
78 | 'transport' => 'failover',
79 | 'mailers' => [
80 | 'smtp',
81 | 'log',
82 | ],
83 | ],
84 |
85 | ],
86 |
87 | /*
88 | |--------------------------------------------------------------------------
89 | | Global "From" Address
90 | |--------------------------------------------------------------------------
91 | |
92 | | You may wish for all emails sent by your application to be sent from
93 | | the same address. Here you may specify a name and address that is
94 | | used globally for all emails that are sent by your application.
95 | |
96 | */
97 |
98 | 'from' => [
99 | 'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'),
100 | 'name' => env('MAIL_FROM_NAME', 'Example'),
101 | ],
102 |
103 | ];
104 |
--------------------------------------------------------------------------------
/frontend/src/components/auth/Login.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import React, { useState } from "react";
3 | import { Input } from "@/components/ui/input";
4 | import { Label } from "@/components/ui/label";
5 | import { TabsContent } from "@/components/ui/tabs";
6 | import { Button } from "@/components/ui/button";
7 | import {
8 | Card,
9 | CardContent,
10 | CardDescription,
11 | CardHeader,
12 | CardTitle,
13 | } from "@/components/ui/card";
14 | import { CHECK_CREDENTIALS } from "@/lib/apiEndPoints";
15 | import { signIn } from "next-auth/react";
16 | import { toast } from "react-toastify";
17 | import myAxios from "@/lib/axios.config";
18 |
19 | export default function Login() {
20 | const [authState, setAuthState] = useState({
21 | email: "",
22 | password: "",
23 | });
24 | const [errors, setErrors] = useState({
25 | email: [],
26 | password: [],
27 | });
28 | const [loading, setLoading] = useState(false);
29 |
30 | const handleSubmit = (event: React.FormEvent) => {
31 | event.preventDefault();
32 | setLoading(true);
33 | myAxios
34 | .post(CHECK_CREDENTIALS, authState)
35 | .then((res) => {
36 | const response = res.data;
37 | setLoading(false);
38 | if (response?.status == 200) {
39 | signIn("credentials", {
40 | email: authState.email,
41 | password: authState.password,
42 | redirect: true,
43 | callbackUrl: "/",
44 | });
45 | } else if (response?.status == 401) {
46 | toast.error(response?.message);
47 | }
48 | })
49 | .catch((err) => {
50 | setLoading(false);
51 | if (err.response?.status == 422) {
52 | setErrors(err.response?.data?.errors);
53 | } else {
54 | toast.error("Something went wrong.please try again!");
55 | }
56 | });
57 | };
58 | return (
59 |
60 |
61 |
62 | Login
63 | Welcome back to Daily Dev.
64 |
65 |
66 |
101 |
102 |
103 |
104 | );
105 | }
106 |
--------------------------------------------------------------------------------
/backend/app/Http/Controllers/API/AuthController.php:
--------------------------------------------------------------------------------
1 | validate([
16 | "name" => "required|min:2|max:50",
17 | "email" => "required|email|unique:users,email",
18 | "username" => "required|alpha_num:ascii|min:4|max:50|unique:users,username",
19 | "password" => "required|min:6|max:50|confirmed"
20 | ]);
21 |
22 | try {
23 | $payload["password"] = Hash::make($payload["password"]);
24 | User::create($payload);
25 | return response()->json(["status" => 200, "message" => "Account created successfully!"]);
26 | } catch (\Exception $err) {
27 | Log::info("user_register_err =>" . $err->getMessage());
28 | return response()->json(["status" => 500, "message" => "Something went wrong!"], 500);
29 | }
30 | }
31 |
32 | // * Login user
33 | public function login(Request $request)
34 | {
35 | $payload = $request->validate([
36 | "email" => "required|email",
37 | "password" => "required"
38 | ]);
39 |
40 | try {
41 | $user = User::where("email", $payload["email"])->first();
42 | if ($user) {
43 | // * Check password
44 | if (!Hash::check($payload["password"], $user->password)) {
45 | return response()->json(["status" => 401, "message" => "Invalid credentials."]);
46 | }
47 |
48 | $token = $user->createToken("web")->plainTextToken;
49 | $authRes = array_merge($user->toArray(), ["token" => $token]);
50 | return ["status" => 200, "user" => $authRes, "message" => "Loggedin succssfully!"];
51 | }
52 | return response()->json(["status" => 401, "message" => "No account found with these credentials."]);
53 | } catch (\Exception $err) {
54 | Log::info("user_register_err =>" . $err->getMessage());
55 | return response()->json(["status" => 500, "message" => "Something went wrong!"], 500);
56 | }
57 | }
58 |
59 | // * check credentials
60 | public function checkCredentias(Request $request)
61 | {
62 | $payload = $request->validate([
63 | "email" => "required|email",
64 | "password" => "required"
65 | ]);
66 |
67 | try {
68 | $user = User::where("email", $payload["email"])->first();
69 | if ($user) {
70 | // * Check password
71 | if (!Hash::check($payload["password"], $user->password)) {
72 | return response()->json(["status" => 401, "message" => "Invalid credentials."]);
73 | }
74 | return ["status" => 200, "message" => "Loggedin succssfully!"];
75 | }
76 | return response()->json(["status" => 401, "message" => "No account found with these credentials."]);
77 | } catch (\Exception $err) {
78 | Log::info("user_register_err =>" . $err->getMessage());
79 | return response()->json(["status" => 500, "message" => "Something went wrong!"], 500);
80 | }
81 | }
82 |
83 | // * Logout
84 | public function logout(Request $request)
85 | {
86 | try {
87 | $request->user()->currentAccessToken()->delete();
88 | return ["status" => 200, "message" => "logged out successfully!"];
89 | } catch (\Exception $err) {
90 | Log::info("user_logout_err =>" . $err->getMessage());
91 | return response()->json(["status" => 500, "message" => "Something went wrong!"], 500);
92 | }
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/backend/config/cache.php:
--------------------------------------------------------------------------------
1 | env('CACHE_STORE', 'database'),
19 |
20 | /*
21 | |--------------------------------------------------------------------------
22 | | Cache Stores
23 | |--------------------------------------------------------------------------
24 | |
25 | | Here you may define all of the cache "stores" for your application as
26 | | well as their drivers. You may even define multiple stores for the
27 | | same cache driver to group types of items stored in your caches.
28 | |
29 | | Supported drivers: "apc", "array", "database", "file", "memcached",
30 | | "redis", "dynamodb", "octane", "null"
31 | |
32 | */
33 |
34 | 'stores' => [
35 |
36 | 'array' => [
37 | 'driver' => 'array',
38 | 'serialize' => false,
39 | ],
40 |
41 | 'database' => [
42 | 'driver' => 'database',
43 | 'table' => env('DB_CACHE_TABLE', 'cache'),
44 | 'connection' => env('DB_CACHE_CONNECTION', null),
45 | 'lock_connection' => env('DB_CACHE_LOCK_CONNECTION', null),
46 | ],
47 |
48 | 'file' => [
49 | 'driver' => 'file',
50 | 'path' => storage_path('framework/cache/data'),
51 | 'lock_path' => storage_path('framework/cache/data'),
52 | ],
53 |
54 | 'memcached' => [
55 | 'driver' => 'memcached',
56 | 'persistent_id' => env('MEMCACHED_PERSISTENT_ID'),
57 | 'sasl' => [
58 | env('MEMCACHED_USERNAME'),
59 | env('MEMCACHED_PASSWORD'),
60 | ],
61 | 'options' => [
62 | // Memcached::OPT_CONNECT_TIMEOUT => 2000,
63 | ],
64 | 'servers' => [
65 | [
66 | 'host' => env('MEMCACHED_HOST', '127.0.0.1'),
67 | 'port' => env('MEMCACHED_PORT', 11211),
68 | 'weight' => 100,
69 | ],
70 | ],
71 | ],
72 |
73 | 'redis' => [
74 | 'driver' => 'redis',
75 | 'connection' => env('REDIS_CACHE_CONNECTION', 'cache'),
76 | 'lock_connection' => env('REDIS_CACHE_LOCK_CONNECTION', 'default'),
77 | ],
78 |
79 | 'dynamodb' => [
80 | 'driver' => 'dynamodb',
81 | 'key' => env('AWS_ACCESS_KEY_ID'),
82 | 'secret' => env('AWS_SECRET_ACCESS_KEY'),
83 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
84 | 'table' => env('DYNAMODB_CACHE_TABLE', 'cache'),
85 | 'endpoint' => env('DYNAMODB_ENDPOINT'),
86 | ],
87 |
88 | 'octane' => [
89 | 'driver' => 'octane',
90 | ],
91 |
92 | ],
93 |
94 | /*
95 | |--------------------------------------------------------------------------
96 | | Cache Key Prefix
97 | |--------------------------------------------------------------------------
98 | |
99 | | When utilizing the APC, database, memcached, Redis, and DynamoDB cache
100 | | stores, there might be other applications using the same cache. For
101 | | that reason, you may prefix every cache key to avoid collisions.
102 | |
103 | */
104 |
105 | 'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache_'),
106 |
107 | ];
108 |
--------------------------------------------------------------------------------
/backend/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | ## About Laravel
11 |
12 | Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable and creative experience to be truly fulfilling. Laravel takes the pain out of development by easing common tasks used in many web projects, such as:
13 |
14 | - [Simple, fast routing engine](https://laravel.com/docs/routing).
15 | - [Powerful dependency injection container](https://laravel.com/docs/container).
16 | - Multiple back-ends for [session](https://laravel.com/docs/session) and [cache](https://laravel.com/docs/cache) storage.
17 | - Expressive, intuitive [database ORM](https://laravel.com/docs/eloquent).
18 | - Database agnostic [schema migrations](https://laravel.com/docs/migrations).
19 | - [Robust background job processing](https://laravel.com/docs/queues).
20 | - [Real-time event broadcasting](https://laravel.com/docs/broadcasting).
21 |
22 | Laravel is accessible, powerful, and provides tools required for large, robust applications.
23 |
24 | ## Learning Laravel
25 |
26 | Laravel has the most extensive and thorough [documentation](https://laravel.com/docs) and video tutorial library of all modern web application frameworks, making it a breeze to get started with the framework.
27 |
28 | You may also try the [Laravel Bootcamp](https://bootcamp.laravel.com), where you will be guided through building a modern Laravel application from scratch.
29 |
30 | If you don't feel like reading, [Laracasts](https://laracasts.com) can help. Laracasts contains thousands of video tutorials on a range of topics including Laravel, modern PHP, unit testing, and JavaScript. Boost your skills by digging into our comprehensive video library.
31 |
32 | ## Laravel Sponsors
33 |
34 | We would like to extend our thanks to the following sponsors for funding Laravel development. If you are interested in becoming a sponsor, please visit the [Laravel Partners program](https://partners.laravel.com).
35 |
36 | ### Premium Partners
37 |
38 | - **[Vehikl](https://vehikl.com/)**
39 | - **[Tighten Co.](https://tighten.co)**
40 | - **[WebReinvent](https://webreinvent.com/)**
41 | - **[Kirschbaum Development Group](https://kirschbaumdevelopment.com)**
42 | - **[64 Robots](https://64robots.com)**
43 | - **[Curotec](https://www.curotec.com/services/technologies/laravel/)**
44 | - **[Cyber-Duck](https://cyber-duck.co.uk)**
45 | - **[DevSquad](https://devsquad.com/hire-laravel-developers)**
46 | - **[Jump24](https://jump24.co.uk)**
47 | - **[Redberry](https://redberry.international/laravel/)**
48 | - **[Active Logic](https://activelogic.com)**
49 | - **[byte5](https://byte5.de)**
50 | - **[OP.GG](https://op.gg)**
51 |
52 | ## Contributing
53 |
54 | Thank you for considering contributing to the Laravel framework! The contribution guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions).
55 |
56 | ## Code of Conduct
57 |
58 | In order to ensure that the Laravel community is welcoming to all, please review and abide by the [Code of Conduct](https://laravel.com/docs/contributions#code-of-conduct).
59 |
60 | ## Security Vulnerabilities
61 |
62 | If you discover a security vulnerability within Laravel, please send an e-mail to Taylor Otwell via [taylor@laravel.com](mailto:taylor@laravel.com). All security vulnerabilities will be promptly addressed.
63 |
64 | ## License
65 |
66 | The Laravel framework is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT).
67 |
--------------------------------------------------------------------------------
/backend/config/queue.php:
--------------------------------------------------------------------------------
1 | env('QUEUE_CONNECTION', 'database'),
17 |
18 | /*
19 | |--------------------------------------------------------------------------
20 | | Queue Connections
21 | |--------------------------------------------------------------------------
22 | |
23 | | Here you may configure the connection options for every queue backend
24 | | used by your application. An example configuration is provided for
25 | | each backend supported by Laravel. You're also free to add more.
26 | |
27 | | Drivers: "sync", "database", "beanstalkd", "sqs", "redis", "null"
28 | |
29 | */
30 |
31 | 'connections' => [
32 |
33 | 'sync' => [
34 | 'driver' => 'sync',
35 | ],
36 |
37 | 'database' => [
38 | 'driver' => 'database',
39 | 'connection' => env('DB_QUEUE_CONNECTION', null),
40 | 'table' => env('DB_QUEUE_TABLE', 'jobs'),
41 | 'queue' => env('DB_QUEUE', 'default'),
42 | 'retry_after' => env('DB_QUEUE_RETRY_AFTER', 90),
43 | 'after_commit' => false,
44 | ],
45 |
46 | 'beanstalkd' => [
47 | 'driver' => 'beanstalkd',
48 | 'host' => env('BEANSTALKD_QUEUE_HOST', 'localhost'),
49 | 'queue' => env('BEANSTALKD_QUEUE', 'default'),
50 | 'retry_after' => env('BEANSTALKD_QUEUE_RETRY_AFTER', 90),
51 | 'block_for' => 0,
52 | 'after_commit' => false,
53 | ],
54 |
55 | 'sqs' => [
56 | 'driver' => 'sqs',
57 | 'key' => env('AWS_ACCESS_KEY_ID'),
58 | 'secret' => env('AWS_SECRET_ACCESS_KEY'),
59 | 'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'),
60 | 'queue' => env('SQS_QUEUE', 'default'),
61 | 'suffix' => env('SQS_SUFFIX'),
62 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
63 | 'after_commit' => false,
64 | ],
65 |
66 | 'redis' => [
67 | 'driver' => 'redis',
68 | 'connection' => env('REDIS_QUEUE_CONNECTION', 'default'),
69 | 'queue' => env('REDIS_QUEUE', 'default'),
70 | 'retry_after' => env('REDIS_QUEUE_RETRY_AFTER', 90),
71 | 'block_for' => null,
72 | 'after_commit' => false,
73 | ],
74 |
75 | ],
76 |
77 | /*
78 | |--------------------------------------------------------------------------
79 | | Job Batching
80 | |--------------------------------------------------------------------------
81 | |
82 | | The following options configure the database and table that store job
83 | | batching information. These options can be updated to any database
84 | | connection and table which has been defined by your application.
85 | |
86 | */
87 |
88 | 'batching' => [
89 | 'database' => env('DB_CONNECTION', 'sqlite'),
90 | 'table' => 'job_batches',
91 | ],
92 |
93 | /*
94 | |--------------------------------------------------------------------------
95 | | Failed Queue Jobs
96 | |--------------------------------------------------------------------------
97 | |
98 | | These options configure the behavior of failed queue job logging so you
99 | | can control how and where failed jobs are stored. Laravel ships with
100 | | support for storing failed jobs in a simple file or in a database.
101 | |
102 | | Supported drivers: "database-uuids", "dynamodb", "file", "null"
103 | |
104 | */
105 |
106 | 'failed' => [
107 | 'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'),
108 | 'database' => env('DB_CONNECTION', 'sqlite'),
109 | 'table' => 'failed_jobs',
110 | ],
111 |
112 | ];
113 |
--------------------------------------------------------------------------------
/frontend/src/components/ui/dialog.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as DialogPrimitive from "@radix-ui/react-dialog"
5 | import { Cross2Icon } from "@radix-ui/react-icons"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const Dialog = DialogPrimitive.Root
10 |
11 | const DialogTrigger = DialogPrimitive.Trigger
12 |
13 | const DialogPortal = DialogPrimitive.Portal
14 |
15 | const DialogClose = DialogPrimitive.Close
16 |
17 | const DialogOverlay = React.forwardRef<
18 | React.ElementRef,
19 | React.ComponentPropsWithoutRef
20 | >(({ className, ...props }, ref) => (
21 |
29 | ))
30 | DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
31 |
32 | const DialogContent = React.forwardRef<
33 | React.ElementRef,
34 | React.ComponentPropsWithoutRef
35 | >(({ className, children, ...props }, ref) => (
36 |
37 |
38 |
46 | {children}
47 |
48 |
49 | Close
50 |
51 |
52 |
53 | ))
54 | DialogContent.displayName = DialogPrimitive.Content.displayName
55 |
56 | const DialogHeader = ({
57 | className,
58 | ...props
59 | }: React.HTMLAttributes) => (
60 |
67 | )
68 | DialogHeader.displayName = "DialogHeader"
69 |
70 | const DialogFooter = ({
71 | className,
72 | ...props
73 | }: React.HTMLAttributes) => (
74 |
81 | )
82 | DialogFooter.displayName = "DialogFooter"
83 |
84 | const DialogTitle = React.forwardRef<
85 | React.ElementRef,
86 | React.ComponentPropsWithoutRef
87 | >(({ className, ...props }, ref) => (
88 |
96 | ))
97 | DialogTitle.displayName = DialogPrimitive.Title.displayName
98 |
99 | const DialogDescription = React.forwardRef<
100 | React.ElementRef,
101 | React.ComponentPropsWithoutRef
102 | >(({ className, ...props }, ref) => (
103 |
108 | ))
109 | DialogDescription.displayName = DialogPrimitive.Description.displayName
110 |
111 | export {
112 | Dialog,
113 | DialogPortal,
114 | DialogOverlay,
115 | DialogTrigger,
116 | DialogClose,
117 | DialogContent,
118 | DialogHeader,
119 | DialogFooter,
120 | DialogTitle,
121 | DialogDescription,
122 | }
123 |
--------------------------------------------------------------------------------
/frontend/src/components/comment/AddComment.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import { Button } from "../ui/button";
3 | import UserAvatar from "../common/UserAvatar";
4 | import { Textarea } from "../ui/textarea";
5 | import { useSession } from "next-auth/react";
6 | import { CustomUser } from "@/app/api/auth/[...nextauth]/authOptions";
7 | import myAxios from "@/lib/axios.config";
8 | import { COMMENT_URL } from "@/lib/apiEndPoints";
9 | import { toast } from "react-toastify";
10 | import CommentCard from "./CommentCard";
11 |
12 | export default function AddComment({ post }: { post: PostApiType }) {
13 | const [showBox, setShowBox] = useState(true);
14 | const { data } = useSession();
15 | const user: CustomUser = data?.user as CustomUser;
16 | const [comment, setComment] = useState("");
17 | const [errors, setErrors] = useState({
18 | post_id: [],
19 | comment: [],
20 | });
21 | const [loading, setLoading] = useState(false);
22 | const [comments, setComments] = useState>();
23 |
24 | useEffect(() => {
25 | fetchComments();
26 | }, []);
27 |
28 | const addComment = (event: React.FormEvent) => {
29 | event.preventDefault();
30 | setLoading(true);
31 | myAxios
32 | .post(
33 | COMMENT_URL,
34 | {
35 | comment,
36 | post_id: post.id,
37 | },
38 | {
39 | headers: {
40 | Authorization: `Bearer ${user.token}`,
41 | },
42 | }
43 | )
44 | .then((res) => {
45 | const response = res.data;
46 | setLoading(false);
47 | setComments((prevState) => {
48 | if (prevState) {
49 | if (prevState?.data.length === 0) {
50 | return {
51 | ...prevState,
52 | data: [response.comment],
53 | };
54 | } else {
55 | return {
56 | ...prevState,
57 | data: [response.comment, ...prevState.data],
58 | };
59 | }
60 | }
61 | });
62 |
63 | setComment("");
64 | toast.success("Comment added successfully!");
65 | })
66 | .catch((err) => {
67 | setLoading(false);
68 | if (err.response?.status === 422) {
69 | setErrors(err.response?.data.errors);
70 | } else {
71 | toast.error("Something went wrong.Please try again!");
72 | }
73 | });
74 | };
75 |
76 | const fetchComments = () => {
77 | myAxios
78 | .get(`${COMMENT_URL}?post_id=${post.id}`, {
79 | headers: {
80 | Authorization: `Bearer ${user.token}`,
81 | },
82 | })
83 | .then((res) => {
84 | setComments(res.data);
85 | console.log("The response is", res.data);
86 | })
87 | .catch((err) => {
88 | toast.error("Someting went wrong while fetching comments!");
89 | });
90 | };
91 |
92 | return (
93 |
94 | {showBox ? (
95 |
setShowBox(false)}
98 | >
99 |
100 |
101 |
Share your thoughts
102 |
103 |
Post
104 |
105 | ) : (
106 |
123 | )}
124 |
125 |
126 | {comments?.data &&
127 | comments.data.length > 0 &&
128 | comments.data.map((item, index) => (
129 |
130 | ))}
131 |
132 |
133 | );
134 | }
135 |
--------------------------------------------------------------------------------
/backend/config/auth.php:
--------------------------------------------------------------------------------
1 | [
17 | 'guard' => env('AUTH_GUARD', 'web'),
18 | 'passwords' => env('AUTH_PASSWORD_BROKER', 'users'),
19 | ],
20 |
21 | /*
22 | |--------------------------------------------------------------------------
23 | | Authentication Guards
24 | |--------------------------------------------------------------------------
25 | |
26 | | Next, you may define every authentication guard for your application.
27 | | Of course, a great default configuration has been defined for you
28 | | which utilizes session storage plus the Eloquent user provider.
29 | |
30 | | All authentication guards have a user provider, which defines how the
31 | | users are actually retrieved out of your database or other storage
32 | | system used by the application. Typically, Eloquent is utilized.
33 | |
34 | | Supported: "session"
35 | |
36 | */
37 |
38 | 'guards' => [
39 | 'web' => [
40 | 'driver' => 'session',
41 | 'provider' => 'users',
42 | ],
43 | ],
44 |
45 | /*
46 | |--------------------------------------------------------------------------
47 | | User Providers
48 | |--------------------------------------------------------------------------
49 | |
50 | | All authentication guards have a user provider, which defines how the
51 | | users are actually retrieved out of your database or other storage
52 | | system used by the application. Typically, Eloquent is utilized.
53 | |
54 | | If you have multiple user tables or models you may configure multiple
55 | | providers to represent the model / table. These providers may then
56 | | be assigned to any extra authentication guards you have defined.
57 | |
58 | | Supported: "database", "eloquent"
59 | |
60 | */
61 |
62 | 'providers' => [
63 | 'users' => [
64 | 'driver' => 'eloquent',
65 | 'model' => env('AUTH_MODEL', App\Models\User::class),
66 | ],
67 |
68 | // 'users' => [
69 | // 'driver' => 'database',
70 | // 'table' => 'users',
71 | // ],
72 | ],
73 |
74 | /*
75 | |--------------------------------------------------------------------------
76 | | Resetting Passwords
77 | |--------------------------------------------------------------------------
78 | |
79 | | These configuration options specify the behavior of Laravel's password
80 | | reset functionality, including the table utilized for token storage
81 | | and the user provider that is invoked to actually retrieve users.
82 | |
83 | | The expiry time is the number of minutes that each reset token will be
84 | | considered valid. This security feature keeps tokens short-lived so
85 | | they have less time to be guessed. You may change this as needed.
86 | |
87 | | The throttle setting is the number of seconds a user must wait before
88 | | generating more password reset tokens. This prevents the user from
89 | | quickly generating a very large amount of password reset tokens.
90 | |
91 | */
92 |
93 | 'passwords' => [
94 | 'users' => [
95 | 'provider' => 'users',
96 | 'table' => env('AUTH_PASSWORD_RESET_TOKEN_TABLE', 'password_reset_tokens'),
97 | 'expire' => 60,
98 | 'throttle' => 60,
99 | ],
100 | ],
101 |
102 | /*
103 | |--------------------------------------------------------------------------
104 | | Password Confirmation Timeout
105 | |--------------------------------------------------------------------------
106 | |
107 | | Here you may define the amount of seconds before a password confirmation
108 | | window expires and users are asked to re-enter their password via the
109 | | confirmation screen. By default, the timeout lasts for three hours.
110 | |
111 | */
112 |
113 | 'password_timeout' => env('AUTH_PASSWORD_TIMEOUT', 10800),
114 |
115 | ];
116 |
--------------------------------------------------------------------------------
/backend/config/app.php:
--------------------------------------------------------------------------------
1 | env('APP_NAME', 'Laravel'),
17 |
18 | /*
19 | |--------------------------------------------------------------------------
20 | | Application Environment
21 | |--------------------------------------------------------------------------
22 | |
23 | | This value determines the "environment" your application is currently
24 | | running in. This may determine how you prefer to configure various
25 | | services the application utilizes. Set this in your ".env" file.
26 | |
27 | */
28 |
29 | 'env' => env('APP_ENV', 'production'),
30 |
31 | /*
32 | |--------------------------------------------------------------------------
33 | | Application Debug Mode
34 | |--------------------------------------------------------------------------
35 | |
36 | | When your application is in debug mode, detailed error messages with
37 | | stack traces will be shown on every error that occurs within your
38 | | application. If disabled, a simple generic error page is shown.
39 | |
40 | */
41 |
42 | 'debug' => (bool) env('APP_DEBUG', false),
43 |
44 | /*
45 | |--------------------------------------------------------------------------
46 | | Application URL
47 | |--------------------------------------------------------------------------
48 | |
49 | | This URL is used by the console to properly generate URLs when using
50 | | the Artisan command line tool. You should set this to the root of
51 | | the application so that it's available within Artisan commands.
52 | |
53 | */
54 |
55 | 'url' => env('APP_URL', 'http://localhost'),
56 |
57 | /*
58 | |--------------------------------------------------------------------------
59 | | Application Timezone
60 | |--------------------------------------------------------------------------
61 | |
62 | | Here you may specify the default timezone for your application, which
63 | | will be used by the PHP date and date-time functions. The timezone
64 | | is set to "UTC" by default as it is suitable for most use cases.
65 | |
66 | */
67 |
68 | 'timezone' => env('APP_TIMEZONE', 'UTC'),
69 |
70 | /*
71 | |--------------------------------------------------------------------------
72 | | Application Locale Configuration
73 | |--------------------------------------------------------------------------
74 | |
75 | | The application locale determines the default locale that will be used
76 | | by Laravel's translation / localization methods. This option can be
77 | | set to any locale for which you plan to have translation strings.
78 | |
79 | */
80 |
81 | 'locale' => env('APP_LOCALE', 'en'),
82 |
83 | 'fallback_locale' => env('APP_FALLBACK_LOCALE', 'en'),
84 |
85 | 'faker_locale' => env('APP_FAKER_LOCALE', 'en_US'),
86 |
87 | /*
88 | |--------------------------------------------------------------------------
89 | | Encryption Key
90 | |--------------------------------------------------------------------------
91 | |
92 | | This key is utilized by Laravel's encryption services and should be set
93 | | to a random, 32 character string to ensure that all encrypted values
94 | | are secure. You should do this prior to deploying the application.
95 | |
96 | */
97 |
98 | 'cipher' => 'AES-256-CBC',
99 |
100 | 'key' => env('APP_KEY'),
101 |
102 | 'previous_keys' => [
103 | ...array_filter(
104 | explode(',', env('APP_PREVIOUS_KEYS', ''))
105 | ),
106 | ],
107 |
108 | /*
109 | |--------------------------------------------------------------------------
110 | | Maintenance Mode Driver
111 | |--------------------------------------------------------------------------
112 | |
113 | | These configuration options determine the driver used to determine and
114 | | manage Laravel's "maintenance mode" status. The "cache" driver will
115 | | allow maintenance mode to be controlled across multiple machines.
116 | |
117 | | Supported drivers: "file", "cache"
118 | |
119 | */
120 |
121 | 'maintenance' => [
122 | 'driver' => env('APP_MAINTENANCE_DRIVER', 'file'),
123 | 'store' => env('APP_MAINTENANCE_STORE', 'database'),
124 | ],
125 |
126 | ];
127 |
--------------------------------------------------------------------------------
/backend/config/logging.php:
--------------------------------------------------------------------------------
1 | env('LOG_CHANNEL', 'stack'),
22 |
23 | /*
24 | |--------------------------------------------------------------------------
25 | | Deprecations Log Channel
26 | |--------------------------------------------------------------------------
27 | |
28 | | This option controls the log channel that should be used to log warnings
29 | | regarding deprecated PHP and library features. This allows you to get
30 | | your application ready for upcoming major versions of dependencies.
31 | |
32 | */
33 |
34 | 'deprecations' => [
35 | 'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'),
36 | 'trace' => env('LOG_DEPRECATIONS_TRACE', false),
37 | ],
38 |
39 | /*
40 | |--------------------------------------------------------------------------
41 | | Log Channels
42 | |--------------------------------------------------------------------------
43 | |
44 | | Here you may configure the log channels for your application. Laravel
45 | | utilizes the Monolog PHP logging library, which includes a variety
46 | | of powerful log handlers and formatters that you're free to use.
47 | |
48 | | Available Drivers: "single", "daily", "slack", "syslog",
49 | | "errorlog", "monolog", "custom", "stack"
50 | |
51 | */
52 |
53 | 'channels' => [
54 |
55 | 'stack' => [
56 | 'driver' => 'stack',
57 | 'channels' => explode(',', env('LOG_STACK', 'single')),
58 | 'ignore_exceptions' => false,
59 | ],
60 |
61 | 'single' => [
62 | 'driver' => 'single',
63 | 'path' => storage_path('logs/laravel.log'),
64 | 'level' => env('LOG_LEVEL', 'debug'),
65 | 'replace_placeholders' => true,
66 | ],
67 |
68 | 'daily' => [
69 | 'driver' => 'daily',
70 | 'path' => storage_path('logs/laravel.log'),
71 | 'level' => env('LOG_LEVEL', 'debug'),
72 | 'days' => env('LOG_DAILY_DAYS', 14),
73 | 'replace_placeholders' => true,
74 | ],
75 |
76 | 'slack' => [
77 | 'driver' => 'slack',
78 | 'url' => env('LOG_SLACK_WEBHOOK_URL'),
79 | 'username' => env('LOG_SLACK_USERNAME', 'Laravel Log'),
80 | 'emoji' => env('LOG_SLACK_EMOJI', ':boom:'),
81 | 'level' => env('LOG_LEVEL', 'critical'),
82 | 'replace_placeholders' => true,
83 | ],
84 |
85 | 'papertrail' => [
86 | 'driver' => 'monolog',
87 | 'level' => env('LOG_LEVEL', 'debug'),
88 | 'handler' => env('LOG_PAPERTRAIL_HANDLER', SyslogUdpHandler::class),
89 | 'handler_with' => [
90 | 'host' => env('PAPERTRAIL_URL'),
91 | 'port' => env('PAPERTRAIL_PORT'),
92 | 'connectionString' => 'tls://'.env('PAPERTRAIL_URL').':'.env('PAPERTRAIL_PORT'),
93 | ],
94 | 'processors' => [PsrLogMessageProcessor::class],
95 | ],
96 |
97 | 'stderr' => [
98 | 'driver' => 'monolog',
99 | 'level' => env('LOG_LEVEL', 'debug'),
100 | 'handler' => StreamHandler::class,
101 | 'formatter' => env('LOG_STDERR_FORMATTER'),
102 | 'with' => [
103 | 'stream' => 'php://stderr',
104 | ],
105 | 'processors' => [PsrLogMessageProcessor::class],
106 | ],
107 |
108 | 'syslog' => [
109 | 'driver' => 'syslog',
110 | 'level' => env('LOG_LEVEL', 'debug'),
111 | 'facility' => env('LOG_SYSLOG_FACILITY', LOG_USER),
112 | 'replace_placeholders' => true,
113 | ],
114 |
115 | 'errorlog' => [
116 | 'driver' => 'errorlog',
117 | 'level' => env('LOG_LEVEL', 'debug'),
118 | 'replace_placeholders' => true,
119 | ],
120 |
121 | 'null' => [
122 | 'driver' => 'monolog',
123 | 'handler' => NullHandler::class,
124 | ],
125 |
126 | 'emergency' => [
127 | 'path' => storage_path('logs/laravel.log'),
128 | ],
129 |
130 | ],
131 |
132 | ];
133 |
--------------------------------------------------------------------------------
/frontend/src/components/ui/sheet.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as SheetPrimitive from "@radix-ui/react-dialog"
5 | import { Cross2Icon } from "@radix-ui/react-icons"
6 | import { cva, type VariantProps } from "class-variance-authority"
7 |
8 | import { cn } from "@/lib/utils"
9 |
10 | const Sheet = SheetPrimitive.Root
11 |
12 | const SheetTrigger = SheetPrimitive.Trigger
13 |
14 | const SheetClose = SheetPrimitive.Close
15 |
16 | const SheetPortal = SheetPrimitive.Portal
17 |
18 | const SheetOverlay = React.forwardRef<
19 | React.ElementRef,
20 | React.ComponentPropsWithoutRef
21 | >(({ className, ...props }, ref) => (
22 |
30 | ))
31 | SheetOverlay.displayName = SheetPrimitive.Overlay.displayName
32 |
33 | const sheetVariants = cva(
34 | "fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
35 | {
36 | variants: {
37 | side: {
38 | top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
39 | bottom:
40 | "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
41 | left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
42 | right:
43 | "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
44 | },
45 | },
46 | defaultVariants: {
47 | side: "right",
48 | },
49 | }
50 | )
51 |
52 | interface SheetContentProps
53 | extends React.ComponentPropsWithoutRef,
54 | VariantProps {}
55 |
56 | const SheetContent = React.forwardRef<
57 | React.ElementRef,
58 | SheetContentProps
59 | >(({ side = "right", className, children, ...props }, ref) => (
60 |
61 |
62 |
67 | {children}
68 |
69 |
70 | Close
71 |
72 |
73 |
74 | ))
75 | SheetContent.displayName = SheetPrimitive.Content.displayName
76 |
77 | const SheetHeader = ({
78 | className,
79 | ...props
80 | }: React.HTMLAttributes) => (
81 |
88 | )
89 | SheetHeader.displayName = "SheetHeader"
90 |
91 | const SheetFooter = ({
92 | className,
93 | ...props
94 | }: React.HTMLAttributes) => (
95 |
102 | )
103 | SheetFooter.displayName = "SheetFooter"
104 |
105 | const SheetTitle = React.forwardRef<
106 | React.ElementRef,
107 | React.ComponentPropsWithoutRef
108 | >(({ className, ...props }, ref) => (
109 |
114 | ))
115 | SheetTitle.displayName = SheetPrimitive.Title.displayName
116 |
117 | const SheetDescription = React.forwardRef<
118 | React.ElementRef,
119 | React.ComponentPropsWithoutRef
120 | >(({ className, ...props }, ref) => (
121 |
126 | ))
127 | SheetDescription.displayName = SheetPrimitive.Description.displayName
128 |
129 | export {
130 | Sheet,
131 | SheetPortal,
132 | SheetOverlay,
133 | SheetTrigger,
134 | SheetClose,
135 | SheetContent,
136 | SheetHeader,
137 | SheetFooter,
138 | SheetTitle,
139 | SheetDescription,
140 | }
141 |
--------------------------------------------------------------------------------
/frontend/src/components/auth/Register.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { useState } from "react";
3 | import { Input } from "@/components/ui/input";
4 | import { Label } from "@/components/ui/label";
5 | import { TabsContent } from "@/components/ui/tabs";
6 | import {
7 | Card,
8 | CardContent,
9 | CardDescription,
10 | CardHeader,
11 | CardTitle,
12 | } from "@/components/ui/card";
13 | import { REGISTER_URL } from "@/lib/apiEndPoints";
14 | import { toast } from "react-toastify";
15 | import { Button } from "../ui/button";
16 | import { signIn } from "next-auth/react";
17 | import myAxios from "@/lib/axios.config";
18 |
19 | export default function Register() {
20 | const [loading, setLoading] = useState(false);
21 | const [authState, setAuthState] = useState({
22 | name: "",
23 | email: "",
24 | username: "",
25 | password: "",
26 | password_confirmation: "",
27 | });
28 | const [errors, setErrors] = useState({
29 | name: [],
30 | email: [],
31 | username: [],
32 | password: [],
33 | });
34 |
35 | const handleSubmit = async (event: React.FormEvent) => {
36 | event.preventDefault();
37 | setLoading(true);
38 | await myAxios
39 | .post(REGISTER_URL, authState)
40 | .then((res) => {
41 | const response = res.data;
42 | setLoading(false);
43 | toast.success("Account created successfully!.");
44 | if (response?.status == 200) {
45 | signIn("credentials", {
46 | email: authState.email,
47 | password: authState.password,
48 | redirect: true,
49 | callbackUrl: "/",
50 | });
51 | }
52 | setLoading(false);
53 | setAuthState({});
54 | })
55 | .catch((err) => {
56 | setLoading(false);
57 | if (err.response?.status == 422) {
58 | setErrors(err.response?.data?.errors);
59 | } else {
60 | toast.error("Something went wrong.please try again!");
61 | }
62 | });
63 | };
64 | return (
65 |
66 |
67 |
68 | Register
69 | Welcome to Daily Dev.
70 |
71 |
72 |
73 |
74 | Name
75 |
82 | setAuthState({ ...authState, name: e.target.value })
83 | }
84 | />
85 | {errors?.name?.[0]}
86 |
87 |
88 | Username
89 |
95 | setAuthState({ ...authState, username: e.target.value })
96 | }
97 | />
98 | {errors?.username?.[0]}
99 |
100 |
101 | Email
102 |
108 | setAuthState({ ...authState, email: e.target.value })
109 | }
110 | />
111 | {errors?.email?.[0]}
112 |
113 |
114 | Password
115 |
122 | setAuthState({ ...authState, password: e.target.value })
123 | }
124 | />
125 | {errors?.password?.[0]}
126 |
127 |
128 | Confirm Password
129 |
136 | setAuthState({
137 | ...authState,
138 | password_confirmation: e.target.value,
139 | })
140 | }
141 | />
142 |
143 |
144 |
145 | {loading ? "Processing.." : "Register"}
146 |
147 |
148 |
149 |
150 |
151 |
152 | );
153 | }
154 |
--------------------------------------------------------------------------------
/frontend/src/components/post/AddPost.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import React, { useState } from "react";
3 | import {
4 | Dialog,
5 | DialogContent,
6 | DialogHeader,
7 | DialogTitle,
8 | DialogTrigger,
9 | } from "@/components/ui/dialog";
10 | import { Link as LinkIcon } from "lucide-react";
11 | import { Label } from "../ui/label";
12 | import { Input } from "../ui/input";
13 | import { isValidUrl } from "@/lib/utils";
14 | import axios from "axios";
15 | import { Textarea } from "../ui/textarea";
16 | import Image from "next/image";
17 | import { toast } from "react-toastify";
18 | import { Button } from "../ui/button";
19 | import { POST_URL } from "@/lib/apiEndPoints";
20 | import myAxios from "@/lib/axios.config";
21 | import { useSession } from "next-auth/react";
22 | import { CustomSession } from "@/app/api/auth/[...nextauth]/authOptions";
23 |
24 | export default function AddPost() {
25 | const { data } = useSession();
26 | const userSession = data as CustomSession;
27 | const [open, setOpen] = useState(false);
28 | const [loading, setLoading] = useState(false);
29 | const [postState, setPostState] = useState({
30 | title: "",
31 | url: "",
32 | description: "",
33 | image_url: "",
34 | });
35 | const [errors, setErrors] = useState({
36 | title: [],
37 | url: [],
38 | description: [],
39 | });
40 |
41 | const loadPreview = async () => {
42 | if (isValidUrl(postState.url!)) {
43 | setLoading(true);
44 | axios
45 | .post("/api/image-preview", { url: postState.url })
46 | .then(async (res) => {
47 | setLoading(false);
48 | const response: ImagePreviewResType = res.data?.data;
49 | const img = response.images.length
50 | ? response.images[0]
51 | : "https://images.unsplash.com/photo-1707343848655-a196bfe88861?q=80&w=3540&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDF8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D";
52 |
53 | setPostState({
54 | ...postState,
55 | image_url: img,
56 | title: response.title,
57 | description: response.description ?? "",
58 | });
59 | })
60 | .catch((err) => {
61 | setLoading(false);
62 | toast.error("Something went wrong while fetching data from url!");
63 | });
64 | }
65 | };
66 |
67 | const handleSubmit = (event: React.FormEvent) => {
68 | event.preventDefault();
69 | setLoading(true);
70 | myAxios
71 | .post(POST_URL, postState, {
72 | headers: {
73 | Authorization: `Bearer ${userSession.user?.token}`,
74 | },
75 | })
76 | .then((res) => {
77 | setLoading(false);
78 | setPostState({});
79 | setOpen(false);
80 | })
81 | .catch((err) => {
82 | setLoading(false);
83 | if (err.response?.status == 422) {
84 | setErrors(err.response?.data?.errors);
85 | } else {
86 | toast.error("Something went wrong.please try again!");
87 | }
88 | });
89 | };
90 |
91 | return (
92 |
93 |
94 | setOpen(true)}
97 | >
98 |
99 |
Submit article
100 |
101 |
102 | e.preventDefault()}
104 | className="overflow-y-scroll max-h-screen"
105 | >
106 |
107 | Add Post
108 |
109 |
110 | {postState.image_url && (
111 |
118 | )}
119 |
120 |
Link
121 |
127 | setPostState({ ...postState, url: e.target.value })
128 | }
129 | onBlur={() => loadPreview()}
130 | />
131 |
132 | Make sure you put correct URL
133 |
134 |
135 | {errors.url?.[0]}
136 |
137 |
138 |
139 | Title
140 |
145 | setPostState({ ...postState, title: e.target.value })
146 | }
147 | />
148 | {errors.title?.[0]}
149 |
150 |
151 | Description
152 |
158 | setPostState({ ...postState, description: e.target.value })
159 | }
160 | />
161 | {errors.description?.[0]}
162 |
163 |
164 |
165 | {loading ? "Processing.." : "Submit"}
166 |
167 |
168 |
169 |
170 |
171 | );
172 | }
173 |
--------------------------------------------------------------------------------
/frontend/src/components/base/ProfileMenu.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import React, { useState } from "react";
3 | import {
4 | DropdownMenu,
5 | DropdownMenuContent,
6 | DropdownMenuItem,
7 | DropdownMenuLabel,
8 | DropdownMenuSeparator,
9 | DropdownMenuTrigger,
10 | } from "@/components/ui/dropdown-menu";
11 | import {
12 | Dialog,
13 | DialogContent,
14 | DialogDescription,
15 | DialogHeader,
16 | DialogTitle,
17 | } from "@/components/ui/dialog";
18 | import Image from "next/image";
19 | import { Button } from "../ui/button";
20 | import { DialogClose } from "@radix-ui/react-dialog";
21 | import { signOut, useSession } from "next-auth/react";
22 | import { Label } from "../ui/label";
23 | import { Input } from "../ui/input";
24 | import myAxios from "@/lib/axios.config";
25 | import { LOGOUT_URL, UPDATE_PROFILE } from "@/lib/apiEndPoints";
26 | import { CustomUser } from "@/app/api/auth/[...nextauth]/authOptions";
27 | import { toast } from "react-toastify";
28 | import { getImageUrl } from "@/lib/utils";
29 |
30 | export default function ProfileMenu({ user }: { user: CustomUser }) {
31 | const [logoutOpen, setLogOutOpen] = useState(false);
32 | const [profileOpen, setProfileOpen] = useState(false);
33 | const [image, setImage] = useState(null);
34 | const [errors, setErrors] = useState({
35 | profile_image: [],
36 | });
37 | const [loading, setLoading] = useState(false);
38 | const { update } = useSession();
39 |
40 | const handleImageChange = (event: React.ChangeEvent) => {
41 | const file = event.target.files?.[0];
42 | if (file) {
43 | setImage(file);
44 | }
45 | };
46 | const logoutUser = async () => {
47 | myAxios
48 | .post(
49 | LOGOUT_URL,
50 | {},
51 | {
52 | headers: {
53 | Authorization: `Bearer ${user.token}`,
54 | },
55 | }
56 | )
57 | .then((res) => {
58 | signOut({
59 | callbackUrl: "/login",
60 | redirect: true,
61 | });
62 | })
63 | .catch((err) => {
64 | toast.error("Something went wrong.Please try again!");
65 | });
66 | };
67 |
68 | const updateProfile = (event: React.FormEvent) => {
69 | event.preventDefault();
70 | setLoading(true);
71 | const formData = new FormData();
72 | formData.append("profile_image", image ?? "");
73 | myAxios
74 | .post(UPDATE_PROFILE, formData, {
75 | headers: {
76 | Authorization: "Bearer " + user.token,
77 | },
78 | })
79 | .then((res) => {
80 | const response = res.data;
81 | setLoading(false);
82 | update({ profile_image: response.image });
83 | toast.success("Profile image updated successfully!");
84 | setProfileOpen(false);
85 | })
86 | .catch((err) => {
87 | setLoading(false);
88 | if (err.response?.status == 422) {
89 | setErrors(err.response?.data?.errors);
90 | } else {
91 | toast.error("Something went wrong.please try again!");
92 | }
93 | });
94 | };
95 | return (
96 | <>
97 | {/* Logout dialog */}
98 |
99 |
100 |
101 | Are you absolutely sure?
102 |
103 | This action expire your session and you have to login back to
104 | access your dashboard.
105 |
106 |
107 |
108 |
109 | Yes Logout!
110 |
111 |
112 | Cancel
113 |
114 |
115 |
116 |
117 |
118 | {/* Profile Image update */}
119 |
120 | e.preventDefault()}>
121 |
122 | Change profile
123 |
124 |
125 |
126 | Profile Image
127 |
133 | {errors.profile_image?.[0]}
134 |
135 |
136 |
137 | {" "}
138 | {loading ? "Processing.." : "Update Profile"}
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 | {user.profile_image ? (
148 |
154 | ) : (
155 |
162 | )}
163 |
164 |
165 | My Account
166 |
167 | setProfileOpen(true)}>
168 | Profile
169 |
170 | setLogOutOpen(true)}>
171 | Logout
172 |
173 |
174 |
175 | >
176 | );
177 | }
178 |
--------------------------------------------------------------------------------
/backend/config/database.php:
--------------------------------------------------------------------------------
1 | env('DB_CONNECTION', 'sqlite'),
20 |
21 | /*
22 | |--------------------------------------------------------------------------
23 | | Database Connections
24 | |--------------------------------------------------------------------------
25 | |
26 | | Below are all of the database connections defined for your application.
27 | | An example configuration is provided for each database system which
28 | | is supported by Laravel. You're free to add / remove connections.
29 | |
30 | */
31 |
32 | 'connections' => [
33 |
34 | 'sqlite' => [
35 | 'driver' => 'sqlite',
36 | 'url' => env('DB_URL'),
37 | 'database' => env('DB_DATABASE', database_path('database.sqlite')),
38 | 'prefix' => '',
39 | 'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
40 | ],
41 |
42 | 'mysql' => [
43 | 'driver' => 'mysql',
44 | 'url' => env('DB_URL'),
45 | 'host' => env('DB_HOST', '127.0.0.1'),
46 | 'port' => env('DB_PORT', '3306'),
47 | 'database' => env('DB_DATABASE', 'laravel'),
48 | 'username' => env('DB_USERNAME', 'root'),
49 | 'password' => env('DB_PASSWORD', ''),
50 | 'unix_socket' => env('DB_SOCKET', ''),
51 | 'charset' => env('DB_CHARSET', 'utf8mb4'),
52 | 'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),
53 | 'prefix' => '',
54 | 'prefix_indexes' => true,
55 | 'strict' => true,
56 | 'engine' => null,
57 | 'options' => extension_loaded('pdo_mysql') ? array_filter([
58 | PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
59 | ]) : [],
60 | ],
61 |
62 | 'mariadb' => [
63 | 'driver' => 'mariadb',
64 | 'url' => env('DB_URL'),
65 | 'host' => env('DB_HOST', '127.0.0.1'),
66 | 'port' => env('DB_PORT', '3306'),
67 | 'database' => env('DB_DATABASE', 'laravel'),
68 | 'username' => env('DB_USERNAME', 'root'),
69 | 'password' => env('DB_PASSWORD', ''),
70 | 'unix_socket' => env('DB_SOCKET', ''),
71 | 'charset' => env('DB_CHARSET', 'utf8mb4'),
72 | 'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),
73 | 'prefix' => '',
74 | 'prefix_indexes' => true,
75 | 'strict' => true,
76 | 'engine' => null,
77 | 'options' => extension_loaded('pdo_mysql') ? array_filter([
78 | PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
79 | ]) : [],
80 | ],
81 |
82 | 'pgsql' => [
83 | 'driver' => 'pgsql',
84 | 'url' => env('DB_URL'),
85 | 'host' => env('DB_HOST', '127.0.0.1'),
86 | 'port' => env('DB_PORT', '5432'),
87 | 'database' => env('DB_DATABASE', 'laravel'),
88 | 'username' => env('DB_USERNAME', 'root'),
89 | 'password' => env('DB_PASSWORD', ''),
90 | 'charset' => env('DB_CHARSET', 'utf8'),
91 | 'prefix' => '',
92 | 'prefix_indexes' => true,
93 | 'search_path' => 'public',
94 | 'sslmode' => 'prefer',
95 | ],
96 |
97 | 'sqlsrv' => [
98 | 'driver' => 'sqlsrv',
99 | 'url' => env('DB_URL'),
100 | 'host' => env('DB_HOST', 'localhost'),
101 | 'port' => env('DB_PORT', '1433'),
102 | 'database' => env('DB_DATABASE', 'laravel'),
103 | 'username' => env('DB_USERNAME', 'root'),
104 | 'password' => env('DB_PASSWORD', ''),
105 | 'charset' => env('DB_CHARSET', 'utf8'),
106 | 'prefix' => '',
107 | 'prefix_indexes' => true,
108 | // 'encrypt' => env('DB_ENCRYPT', 'yes'),
109 | // 'trust_server_certificate' => env('DB_TRUST_SERVER_CERTIFICATE', 'false'),
110 | ],
111 |
112 | ],
113 |
114 | /*
115 | |--------------------------------------------------------------------------
116 | | Migration Repository Table
117 | |--------------------------------------------------------------------------
118 | |
119 | | This table keeps track of all the migrations that have already run for
120 | | your application. Using this information, we can determine which of
121 | | the migrations on disk haven't actually been run on the database.
122 | |
123 | */
124 |
125 | 'migrations' => [
126 | 'table' => 'migrations',
127 | 'update_date_on_publish' => true,
128 | ],
129 |
130 | /*
131 | |--------------------------------------------------------------------------
132 | | Redis Databases
133 | |--------------------------------------------------------------------------
134 | |
135 | | Redis is an open source, fast, and advanced key-value store that also
136 | | provides a richer body of commands than a typical key-value system
137 | | such as Memcached. You may define your connection settings here.
138 | |
139 | */
140 |
141 | 'redis' => [
142 |
143 | 'client' => env('REDIS_CLIENT', 'phpredis'),
144 |
145 | 'options' => [
146 | 'cluster' => env('REDIS_CLUSTER', 'redis'),
147 | 'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'),
148 | ],
149 |
150 | 'default' => [
151 | 'url' => env('REDIS_URL'),
152 | 'host' => env('REDIS_HOST', '127.0.0.1'),
153 | 'username' => env('REDIS_USERNAME'),
154 | 'password' => env('REDIS_PASSWORD'),
155 | 'port' => env('REDIS_PORT', '6379'),
156 | 'database' => env('REDIS_DB', '0'),
157 | ],
158 |
159 | 'cache' => [
160 | 'url' => env('REDIS_URL'),
161 | 'host' => env('REDIS_HOST', '127.0.0.1'),
162 | 'username' => env('REDIS_USERNAME'),
163 | 'password' => env('REDIS_PASSWORD'),
164 | 'port' => env('REDIS_PORT', '6379'),
165 | 'database' => env('REDIS_CACHE_DB', '1'),
166 | ],
167 |
168 | ],
169 |
170 | ];
171 |
--------------------------------------------------------------------------------
/frontend/public/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 | Full Logo- Dark/W Logo
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/frontend/src/components/ui/dropdown-menu.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
5 | import {
6 | CheckIcon,
7 | ChevronRightIcon,
8 | DotFilledIcon,
9 | } from "@radix-ui/react-icons"
10 |
11 | import { cn } from "@/lib/utils"
12 |
13 | const DropdownMenu = DropdownMenuPrimitive.Root
14 |
15 | const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
16 |
17 | const DropdownMenuGroup = DropdownMenuPrimitive.Group
18 |
19 | const DropdownMenuPortal = DropdownMenuPrimitive.Portal
20 |
21 | const DropdownMenuSub = DropdownMenuPrimitive.Sub
22 |
23 | const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
24 |
25 | const DropdownMenuSubTrigger = React.forwardRef<
26 | React.ElementRef,
27 | React.ComponentPropsWithoutRef & {
28 | inset?: boolean
29 | }
30 | >(({ className, inset, children, ...props }, ref) => (
31 |
40 | {children}
41 |
42 |
43 | ))
44 | DropdownMenuSubTrigger.displayName =
45 | DropdownMenuPrimitive.SubTrigger.displayName
46 |
47 | const DropdownMenuSubContent = React.forwardRef<
48 | React.ElementRef,
49 | React.ComponentPropsWithoutRef
50 | >(({ className, ...props }, ref) => (
51 |
59 | ))
60 | DropdownMenuSubContent.displayName =
61 | DropdownMenuPrimitive.SubContent.displayName
62 |
63 | const DropdownMenuContent = React.forwardRef<
64 | React.ElementRef,
65 | React.ComponentPropsWithoutRef
66 | >(({ className, sideOffset = 4, ...props }, ref) => (
67 |
68 |
78 |
79 | ))
80 | DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
81 |
82 | const DropdownMenuItem = React.forwardRef<
83 | React.ElementRef,
84 | React.ComponentPropsWithoutRef & {
85 | inset?: boolean
86 | }
87 | >(({ className, inset, ...props }, ref) => (
88 |
97 | ))
98 | DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
99 |
100 | const DropdownMenuCheckboxItem = React.forwardRef<
101 | React.ElementRef,
102 | React.ComponentPropsWithoutRef
103 | >(({ className, children, checked, ...props }, ref) => (
104 |
113 |
114 |
115 |
116 |
117 |
118 | {children}
119 |
120 | ))
121 | DropdownMenuCheckboxItem.displayName =
122 | DropdownMenuPrimitive.CheckboxItem.displayName
123 |
124 | const DropdownMenuRadioItem = React.forwardRef<
125 | React.ElementRef,
126 | React.ComponentPropsWithoutRef
127 | >(({ className, children, ...props }, ref) => (
128 |
136 |
137 |
138 |
139 |
140 |
141 | {children}
142 |
143 | ))
144 | DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
145 |
146 | const DropdownMenuLabel = React.forwardRef<
147 | React.ElementRef,
148 | React.ComponentPropsWithoutRef & {
149 | inset?: boolean
150 | }
151 | >(({ className, inset, ...props }, ref) => (
152 |
161 | ))
162 | DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
163 |
164 | const DropdownMenuSeparator = React.forwardRef<
165 | React.ElementRef,
166 | React.ComponentPropsWithoutRef
167 | >(({ className, ...props }, ref) => (
168 |
173 | ))
174 | DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
175 |
176 | const DropdownMenuShortcut = ({
177 | className,
178 | ...props
179 | }: React.HTMLAttributes) => {
180 | return (
181 |
185 | )
186 | }
187 | DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
188 |
189 | export {
190 | DropdownMenu,
191 | DropdownMenuTrigger,
192 | DropdownMenuContent,
193 | DropdownMenuItem,
194 | DropdownMenuCheckboxItem,
195 | DropdownMenuRadioItem,
196 | DropdownMenuLabel,
197 | DropdownMenuSeparator,
198 | DropdownMenuShortcut,
199 | DropdownMenuGroup,
200 | DropdownMenuPortal,
201 | DropdownMenuSub,
202 | DropdownMenuSubContent,
203 | DropdownMenuSubTrigger,
204 | DropdownMenuRadioGroup,
205 | }
206 |
--------------------------------------------------------------------------------
/backend/config/session.php:
--------------------------------------------------------------------------------
1 | env('SESSION_DRIVER', 'database'),
22 |
23 | /*
24 | |--------------------------------------------------------------------------
25 | | Session Lifetime
26 | |--------------------------------------------------------------------------
27 | |
28 | | Here you may specify the number of minutes that you wish the session
29 | | to be allowed to remain idle before it expires. If you want them
30 | | to expire immediately when the browser is closed then you may
31 | | indicate that via the expire_on_close configuration option.
32 | |
33 | */
34 |
35 | 'lifetime' => env('SESSION_LIFETIME', 120),
36 |
37 | 'expire_on_close' => env('SESSION_EXPIRE_ON_CLOSE', false),
38 |
39 | /*
40 | |--------------------------------------------------------------------------
41 | | Session Encryption
42 | |--------------------------------------------------------------------------
43 | |
44 | | This option allows you to easily specify that all of your session data
45 | | should be encrypted before it's stored. All encryption is performed
46 | | automatically by Laravel and you may use the session like normal.
47 | |
48 | */
49 |
50 | 'encrypt' => env('SESSION_ENCRYPT', false),
51 |
52 | /*
53 | |--------------------------------------------------------------------------
54 | | Session File Location
55 | |--------------------------------------------------------------------------
56 | |
57 | | When utilizing the "file" session driver, the session files are placed
58 | | on disk. The default storage location is defined here; however, you
59 | | are free to provide another location where they should be stored.
60 | |
61 | */
62 |
63 | 'files' => storage_path('framework/sessions'),
64 |
65 | /*
66 | |--------------------------------------------------------------------------
67 | | Session Database Connection
68 | |--------------------------------------------------------------------------
69 | |
70 | | When using the "database" or "redis" session drivers, you may specify a
71 | | connection that should be used to manage these sessions. This should
72 | | correspond to a connection in your database configuration options.
73 | |
74 | */
75 |
76 | 'connection' => env('SESSION_CONNECTION'),
77 |
78 | /*
79 | |--------------------------------------------------------------------------
80 | | Session Database Table
81 | |--------------------------------------------------------------------------
82 | |
83 | | When using the "database" session driver, you may specify the table to
84 | | be used to store sessions. Of course, a sensible default is defined
85 | | for you; however, you're welcome to change this to another table.
86 | |
87 | */
88 |
89 | 'table' => env('SESSION_TABLE', 'sessions'),
90 |
91 | /*
92 | |--------------------------------------------------------------------------
93 | | Session Cache Store
94 | |--------------------------------------------------------------------------
95 | |
96 | | When using one of the framework's cache driven session backends, you may
97 | | define the cache store which should be used to store the session data
98 | | between requests. This must match one of your defined cache stores.
99 | |
100 | | Affects: "apc", "dynamodb", "memcached", "redis"
101 | |
102 | */
103 |
104 | 'store' => env('SESSION_STORE'),
105 |
106 | /*
107 | |--------------------------------------------------------------------------
108 | | Session Sweeping Lottery
109 | |--------------------------------------------------------------------------
110 | |
111 | | Some session drivers must manually sweep their storage location to get
112 | | rid of old sessions from storage. Here are the chances that it will
113 | | happen on a given request. By default, the odds are 2 out of 100.
114 | |
115 | */
116 |
117 | 'lottery' => [2, 100],
118 |
119 | /*
120 | |--------------------------------------------------------------------------
121 | | Session Cookie Name
122 | |--------------------------------------------------------------------------
123 | |
124 | | Here you may change the name of the session cookie that is created by
125 | | the framework. Typically, you should not need to change this value
126 | | since doing so does not grant a meaningful security improvement.
127 | |
128 | |
129 | */
130 |
131 | 'cookie' => env(
132 | 'SESSION_COOKIE',
133 | Str::slug(env('APP_NAME', 'laravel'), '_').'_session'
134 | ),
135 |
136 | /*
137 | |--------------------------------------------------------------------------
138 | | Session Cookie Path
139 | |--------------------------------------------------------------------------
140 | |
141 | | The session cookie path determines the path for which the cookie will
142 | | be regarded as available. Typically, this will be the root path of
143 | | your application, but you're free to change this when necessary.
144 | |
145 | */
146 |
147 | 'path' => env('SESSION_PATH', '/'),
148 |
149 | /*
150 | |--------------------------------------------------------------------------
151 | | Session Cookie Domain
152 | |--------------------------------------------------------------------------
153 | |
154 | | This value determines the domain and subdomains the session cookie is
155 | | available to. By default, the cookie will be available to the root
156 | | domain and all subdomains. Typically, this shouldn't be changed.
157 | |
158 | */
159 |
160 | 'domain' => env('SESSION_DOMAIN'),
161 |
162 | /*
163 | |--------------------------------------------------------------------------
164 | | HTTPS Only Cookies
165 | |--------------------------------------------------------------------------
166 | |
167 | | By setting this option to true, session cookies will only be sent back
168 | | to the server if the browser has a HTTPS connection. This will keep
169 | | the cookie from being sent to you when it can't be done securely.
170 | |
171 | */
172 |
173 | 'secure' => env('SESSION_SECURE_COOKIE'),
174 |
175 | /*
176 | |--------------------------------------------------------------------------
177 | | HTTP Access Only
178 | |--------------------------------------------------------------------------
179 | |
180 | | Setting this value to true will prevent JavaScript from accessing the
181 | | value of the cookie and the cookie will only be accessible through
182 | | the HTTP protocol. It's unlikely you should disable this option.
183 | |
184 | */
185 |
186 | 'http_only' => env('SESSION_HTTP_ONLY', true),
187 |
188 | /*
189 | |--------------------------------------------------------------------------
190 | | Same-Site Cookies
191 | |--------------------------------------------------------------------------
192 | |
193 | | This option determines how your cookies behave when cross-site requests
194 | | take place, and can be used to mitigate CSRF attacks. By default, we
195 | | will set this value to "lax" to permit secure cross-site requests.
196 | |
197 | | See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#samesitesamesite-value
198 | |
199 | | Supported: "lax", "strict", "none", null
200 | |
201 | */
202 |
203 | 'same_site' => env('SESSION_SAME_SITE', 'lax'),
204 |
205 | /*
206 | |--------------------------------------------------------------------------
207 | | Partitioned Cookies
208 | |--------------------------------------------------------------------------
209 | |
210 | | Setting this value to true will tie the cookie to the top-level site for
211 | | a cross-site context. Partitioned cookies are accepted by the browser
212 | | when flagged "secure" and the Same-Site attribute is set to "none".
213 | |
214 | */
215 |
216 | 'partitioned' => env('SESSION_PARTITIONED_COOKIE', false),
217 |
218 | ];
219 |
--------------------------------------------------------------------------------