diff --git a/template/FullStack/Vue(Frontend)+GoFiber(Backend)/README.md b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/README.md new file mode 100644 index 0000000..f5b48be --- /dev/null +++ b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/README.md @@ -0,0 +1,293 @@ +# FullStack Template: Vue.js + Go Fiber + +This repository provides a simple FullStack template for building modern web applications using **Vue.js** for the frontend and **Go Fiber** framework for the backend. It includes essential features such as: CRUD operations, user authentication, session management, and a responsive user interface. + +## Features +- **Frontend**: Built with Vue.js, offering a dynamic and responsive user interface. +- **Backend**: Powered by Go Fiber, providing a robust RESTful API. +- **Session Management**: Utilizes JSON Web Tokens (JWT) to manage user authentication. +- **User Authentication**: Supports user registration, login, and logout with session management using JWT. +- **Snippet Management**: Users can create, read, update, and delete code snippets with syntax highlighting and tagging support. +- **Public and Private Snippets**: Users can mark snippets as public or private. +- **Tagging and Browsing by Tags**: Snippets can be tagged, and users can browse snippets by tags. +- **Syntax Highlighting**: Supports syntax highlighting for multiple programming languages using rismjs. +--- + + +# Screenshots: + + +### Public Snippets + + +### Login + + +### Private Snippets + + +### Add new Snippet + + +### Account + +--- + +## Technologies Used + +### Frontend + +- **Vue.js 3**: A progressive JavaScript framework for building user interfaces. +- **Vue Router**: Handles navigation and routing in the application. +- **Pinia**: State management library for Vue.js applications. +- **Pinia Plugin Persistedstate**: Persists the state of Pinia stores across sessions. +- **Tailwind CSS**: Utility-first CSS framework for rapid UI development. +- **Vue Toastification**: For displaying toast notifications. +- **Prism.js**: For syntax highlighting of code snippets. +- **Vue3 Code Block**: A component for displaying code blocks with syntax highlighting. +- **Axios**: For making HTTP requests to the backend API. + +### Backend + +- **Go**: Modern language well-suited for efficient backends. +- **Go Fiber**: An Express-inspired web framework written in Go. +- **GORM**: An ORM library for Go, used for database operations. +- **PostgreSQL**: Relational database for storing user data and snippets. +- **JWT (dgrijalva/jwt-go)**: Create and verify JWTs for user authentication. +- **bcrypt**: Used for hashing user passwords securely. +- **godotenv**: Loads environment variables from a `.env` file. + +## Installation + +### Prerequisites + +Ensure that **Go**, **Node.js**, and **PostgreSQL** are installed and running on your system. + +### Steps + +1. **Backend Setup**: + + - **Install Dependencies**: + + ```bash + cd server + go mod tidy + ``` + + - **Environment Variables**: + + Create a `.env` file in the `server` directory: + + ```bash + # server/.env + POSTGRES_URL="postgresql://:@:/" + JWT_SECRET=password + PORT=3000 + ALLOWED_ORIGINS=http://localhost:5173 + ADMIN_USERNAME=admin + ADMIN_EMAIL=admin@example.com + ADMIN_PASSWORD=password + ``` + + + - ⚠️ **Important : Database Setup** + + Make sure that your PostgreSQL database is running. Edit the `.env` `POSTGRES_URL` value to match your setup + + - **Run the Backend Server**: + + ```bash + go run . + ``` + + The backend server will automatically migrate the database schema and seed initial data. + +2. **Frontend Setup**: + + - **Install Dependencies**: + + ```bash + cd client + npm install + ``` + + - **Environment Variables** (Optional): + + If your backend API is running on a different URL than `http://localhost:3000`, create a `.env` file in the `client` directory and set the `VITE_BACKEND_URL` variable. + + ```bash + # client/.env + VITE_BACKEND_URL=http://localhost:3000 + ``` + + - **Run the Frontend**: + + ```bash + npm run dev + ``` + +3. **Access the Application** at `http://localhost:5173/` + +## Routes and Functionalities + +### Backend API Endpoints + +#### Authentication + +- **`/api/auth/signup` [POST]**: + - Handles user registration. + - **Request Body**: + - `username`: String + - `emailid`: String + - `password`: String + +- **`/api/auth/signin` [POST]**: + - Handles user login. + - **Request Body**: + - `emailid`: String + - `password`: String + +#### User + +- **`/api/user/signout` [POST]**: + - Logs the user out by clearing the JWT token. + +- **`/api/user/profile` [GET]**: + - Retrieves the user profile information of the authenticated user. + - **Requires Authentication**. + +- **`/api/user/test` [GET]**: + - Test route to verify the API is working. + +#### Snippets + +- **`/api/snippets` [GET]**: + - Retrieves all public snippets. + +- **`/api/snippets/languages` [GET]**: + - Retrieves the list of supported programming languages. + +- **`/api/snippets/user` [GET]**: + - Retrieves the authenticated user's snippets. + - **Requires Authentication**. + +- **`/api/snippets/:id` [GET]**: + - Retrieves a specific snippet by ID. + - **Requires Authentication** for private snippets. + +- **`/api/snippets` [POST]**: + - Creates a new snippet. + - **Requires Authentication**. + - **Request Body**: + - `name`: String + - `description`: String + - `code`: String + - `language`: String + - `tags`: String (comma-separated) + - `visibility`: Boolean (true for public, false for private) + +- **`/api/snippets/:id` [PUT]**: + - Updates a snippet. + - **Requires Authentication**. + - **Request Body**: Same as for creating a snippet. + +- **`/api/snippets/:id` [DELETE]**: + - Deletes a snippet. + - **Requires Authentication**. + +### Frontend Routes + +- **`/` [GET]**: + - Displays all public snippets. + +- **`/signin` [GET]**: + - Sign-in page. + +- **`/signup` [GET]**: + - Sign-up page. + +- **`/account` [GET]**: + - User's account page, displaying user information. + - **Requires Authentication**. + +- **`/snippets` [GET]**: + - Displays the authenticated user's snippets. + - **Requires Authentication**. + +- **`/add-snippet` [GET]**: + - Form to add a new snippet. + - **Requires Authentication**. + +- **`/edit-snippet/:id` [GET]**: + - Form to edit an existing snippet. + - **Requires Authentication**. + +- **`/tag/:tag` [GET]**: + - Displays all public snippets that have the specified tag. + +## Flash Messages + +The application uses toast notifications to communicate various events to the user: + +- **Signup**: + - **Success**: "Signup successful! Please sign in." + - **Error**: Display server error message. + +- **Signin**: + - **Success**: "Signed in successfully!" + - **Error**: Display server error message. + +- **Signout**: + - **Success**: "Sign out successful!" + - **Error**: Display server error message. + +- **Snippet Operations**: + - **Add Snippet Success**: "Snippet added successfully." + - **Edit Snippet Success**: "Snippet updated successfully." + - **Delete Snippet Success**: "Snippet deleted successfully." + - **Error**: Display server error message. + +These messages are displayed on the frontend using Vue Toastification. + +## Database + +The application uses **PostgreSQL** for persistence. + +### Models + +#### User + +- **ID**: Unique identifier for each user. +- **Username**: Unique username. +- **Email**: Unique email address. +- **Password**: Hashed password. + +#### Snippet + +- **ID**: Unique identifier for each snippet. +- **Name**: Name of the snippet. +- **Description**: Description of the snippet. +- **Code**: The code content. +- **Language**: Programming language of the snippet. +- **Tags**: Comma-separated tags associated with the snippet. +- **Visibility**: Boolean indicating if the snippet is public or private. +- **UserID**: The ID of the user who owns the snippet. + +#### Language + +- **ID**: Unique identifier for each language. +- **Name**: Name of the language. + +### Seeding Data + +Upon first run, the backend server will seed initial data: + +- **Languages**: A list of supported languages loaded from `data/languages.json`. +- **Admin User**: An admin user with credentials specified in the `.env` file. +- **Sample Snippets**: Sample code snippets loaded from `data/snippets.yaml`. + +--- + +Made using [Universal-Box](https://github.com/Abhishek-Mallick/universal-box) + +--- \ No newline at end of file diff --git a/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/.env.example b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/.env.example new file mode 100644 index 0000000..43275c7 --- /dev/null +++ b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/.env.example @@ -0,0 +1 @@ +VITE_BACKEND_URL=http://localhost:3000 \ No newline at end of file diff --git a/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/index.html b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/index.html new file mode 100644 index 0000000..59b96a4 --- /dev/null +++ b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/index.html @@ -0,0 +1,13 @@ + + + + + + + Go Fiber + Vue Template + + +
+ + + diff --git a/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/package.json b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/package.json new file mode 100755 index 0000000..e416c79 --- /dev/null +++ b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/package.json @@ -0,0 +1,31 @@ +{ + "name": "vue-frontend", + "version": "1.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "lint": "eslint ." + }, + "dependencies": { + "@vueuse/motion": "^2.2.5", + "axios": "^1.4.0", + "pinia": "^2.1.0", + "pinia-plugin-persistedstate": "^4.1.1", + "prismjs": "^1.29.0", + "vue": "^3.3.4", + "vue-router": "^4.2.3", + "vue-toastification": "next", + "vue3-code-block": "^2.2.14" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.1.4", + "vite": "^5.4.8", + "autoprefixer": "^10.4.7", + "eslint": "^8.28.0", + "postcss": "^8.4.14", + "tailwindcss": "^3.4.10" + } +} \ No newline at end of file diff --git a/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/postcss.config.js b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/postcss.config.js new file mode 100755 index 0000000..88a3558 --- /dev/null +++ b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, + } \ No newline at end of file diff --git a/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/public/logo.webp b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/public/logo.webp new file mode 100755 index 0000000..ffc4377 Binary files /dev/null and b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/public/logo.webp differ diff --git a/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/App.vue b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/App.vue new file mode 100755 index 0000000..bec648d --- /dev/null +++ b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/App.vue @@ -0,0 +1,51 @@ + + + + + \ No newline at end of file diff --git a/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/api.js b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/api.js new file mode 100644 index 0000000..b2b9f3e --- /dev/null +++ b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/api.js @@ -0,0 +1,38 @@ +import axios from 'axios' +const baseURL = import.meta.env.VITE_BACKEND_URL || 'http://localhost:3000' + +const api = axios.create({ + baseURL, + withCredentials: true, + timeout: 10000, +}) + +api.interceptors.response.use( + (response) => response, + (error) => { + if (error.response && error.response.status === 401) { + return Promise.reject(error) + } + console.error('API Error:', error) + return Promise.reject(error) + } +) + +// Snippet endpoints +export const getPublicSnippets = () => api.get('/api/snippets') +export const getUserSnippets = () => api.get('/api/snippets/user') +export const getSnippet = (id) => api.get(`/api/snippets/${id}`) +export const createSnippet = (snippetData) => api.post('/api/snippets', snippetData) +export const updateSnippet = (id, snippetData) => api.put(`/api/snippets/${id}`, snippetData) +export const deleteSnippet = (id) => api.delete(`/api/snippets/${id}`) + +// Language endpoints +export const getLanguages = () => api.get('/api/snippets/languages') + +// Auth endpoints +export const signin = (credentials) => api.post('/api/auth/signin', credentials) +export const signup = (userData) => api.post('/api/auth/signup', userData) +export const signout = () => api.post('/api/user/signout') +export const getProfile = () => api.get('/api/user/profile') + +export default api \ No newline at end of file diff --git a/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/components/BaseForm.vue b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/components/BaseForm.vue new file mode 100644 index 0000000..600b8db --- /dev/null +++ b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/components/BaseForm.vue @@ -0,0 +1,40 @@ + + + \ No newline at end of file diff --git a/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/components/BaseInput.vue b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/components/BaseInput.vue new file mode 100644 index 0000000..b3b4672 --- /dev/null +++ b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/components/BaseInput.vue @@ -0,0 +1,31 @@ + + + \ No newline at end of file diff --git a/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/components/BaseTextarea.vue b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/components/BaseTextarea.vue new file mode 100644 index 0000000..b4e0614 --- /dev/null +++ b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/components/BaseTextarea.vue @@ -0,0 +1,31 @@ + + + \ No newline at end of file diff --git a/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/components/Footer.vue b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/components/Footer.vue new file mode 100755 index 0000000..2b0c879 --- /dev/null +++ b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/components/Footer.vue @@ -0,0 +1,10 @@ + + + diff --git a/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/components/Header.vue b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/components/Header.vue new file mode 100755 index 0000000..21548c1 --- /dev/null +++ b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/components/Header.vue @@ -0,0 +1,112 @@ + + + + diff --git a/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/components/LoadingWrapper.vue b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/components/LoadingWrapper.vue new file mode 100644 index 0000000..0f78f26 --- /dev/null +++ b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/components/LoadingWrapper.vue @@ -0,0 +1,20 @@ + + + \ No newline at end of file diff --git a/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/components/PageContainer.vue b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/components/PageContainer.vue new file mode 100644 index 0000000..78b2793 --- /dev/null +++ b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/components/PageContainer.vue @@ -0,0 +1,12 @@ + + + \ No newline at end of file diff --git a/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/components/SnippetForm.vue b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/components/SnippetForm.vue new file mode 100644 index 0000000..50f99c9 --- /dev/null +++ b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/components/SnippetForm.vue @@ -0,0 +1,97 @@ + + + \ No newline at end of file diff --git a/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/components/SnippetsList.vue b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/components/SnippetsList.vue new file mode 100644 index 0000000..cd91aa0 --- /dev/null +++ b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/components/SnippetsList.vue @@ -0,0 +1,142 @@ + + + + + \ No newline at end of file diff --git a/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/components/Spinner.vue b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/components/Spinner.vue new file mode 100644 index 0000000..9a2958e --- /dev/null +++ b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/components/Spinner.vue @@ -0,0 +1,25 @@ + + + + + + \ No newline at end of file diff --git a/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/composables/useForm.js b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/composables/useForm.js new file mode 100644 index 0000000..6fb6b6e --- /dev/null +++ b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/composables/useForm.js @@ -0,0 +1,75 @@ +import { reactive, ref } from 'vue' +import { useToast } from 'vue-toastification' + +// Check if an email is valid +const validEmail = (email) => { + const re = /\S+@\S+\.\S+/ + return re.test(email) +} + +// Composable for form state, validation, and submission +export const useForm = (initialState, validations, submitAction) => { + + const formData = reactive({ ...initialState }) + const errors = reactive({}) + const serverError = ref('') + const successMessage = ref('') + const loading = ref(false) + const toast = useToast() + + // Validates the form fields using the rules + const validateForm = () => { + let isValid = true + Object.keys(validations).forEach(field => { + const error = validations[field](formData[field]) + if (error) { + errors[field] = error + isValid = false + } else { + errors[field] = '' + } + }) + return isValid + } + + // form submission + const handleSubmit = async () => { + serverError.value = '' + successMessage.value = '' + + if (!validateForm()) return + + try { + loading.value = true + const message = await submitAction(formData) + loading.value = false + successMessage.value = message + toast.success(message) + if (handleSubmit.onSuccess) handleSubmit.onSuccess() + } catch (error) { + loading.value = false + serverError.value = error.response?.data?.message || error.message || 'An error occurred' + toast.error(serverError.value) + } + } + + return { + formData, + errors, + serverError, + successMessage, + loading, + handleSubmit, + validateForm, + } +} + +// validation helper +export const commonValidations = { + required: (fieldName) => (value) => + value ? '' : `${fieldName} is required`, + minLength: (fieldName, minLength) => (value) => + value.length >= minLength ? '' : `${fieldName} must be at least ${minLength} characters`, + email: (value) => + validEmail(value) ? '' : 'Invalid email format', +} \ No newline at end of file diff --git a/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/index.css b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/index.css new file mode 100755 index 0000000..be48dfc --- /dev/null +++ b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/index.css @@ -0,0 +1,18 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +/* Spinner */ +.spinner { + border: 4px solid rgba(255, 255, 255, 0.3); + border-radius: 50%; + border-top: 4px solid #ffffff; + width: 40px; + height: 40px; + animation: spin 1s linear infinite; +} + +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} diff --git a/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/main.js b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/main.js new file mode 100755 index 0000000..8523992 --- /dev/null +++ b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/main.js @@ -0,0 +1,41 @@ + +import { createApp } from 'vue' +import App from './App.vue' +import router from './router' +import { createPinia } from 'pinia' +import { MotionPlugin } from '@vueuse/motion' +import Toast from 'vue-toastification' +import 'vue-toastification/dist/index.css' +import './index.css' +import piniaPluginPersistedstate from 'pinia-plugin-persistedstate' + +// Import Prism.js CSS +import 'prismjs/themes/prism-tomorrow.css' + +const app = createApp(App) + +const pinia = createPinia() +pinia.use(piniaPluginPersistedstate) + +// Vue Toastification +const toastOptions = { + position: "top-left", + timeout: 3000, + closeOnClick: true, + pauseOnFocusLoss: true, + pauseOnHover: true, + draggable: true, + draggablePercent: 0.6, + showCloseButtonOnHover: false, + hideProgressBar: true, + closeButton: "button", + icon: true, + rtl: false +} + +app.use(MotionPlugin) +app.use(pinia) +app.use(router) +app.use(Toast, toastOptions) + +app.mount('#app') \ No newline at end of file diff --git a/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/router/index.js b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/router/index.js new file mode 100755 index 0000000..811d0ed --- /dev/null +++ b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/router/index.js @@ -0,0 +1,82 @@ +import { createRouter, createWebHistory } from 'vue-router' +import Home from '../views/Home.vue' +import Signin from '../views/Signin.vue' +import Signup from '../views/Signup.vue' +import Account from '../views/Account.vue' +import Snippets from '../views/Snippets.vue' +import AddSnippet from '../views/AddSnippet.vue' +import EditSnippet from '../views/EditSnippet.vue' +import TaggedSnippets from '../views/TaggedSnippets.vue' +import { useAuthStore } from '../stores/auth' + +const routes = [ + { + path: '/', + name: 'Home', + component: Home + }, + { + path: '/signin', + name: 'Signin', + component: Signin, + meta: { requiresGuest: true } + }, + { + path: '/signup', + name: 'Signup', + component: Signup, + meta: { requiresGuest: true } + }, + { + path: '/account', + name: 'Account', + component: Account, + meta: { requiresAuth: true } + }, + { + path: '/snippets', + name: 'Snippets', + component: Snippets, + meta: { requiresAuth: true } + }, + { + path: '/add-snippet', + name: 'AddSnippet', + component: AddSnippet, + meta: { requiresAuth: true, transition: 'slide-fade' } + }, + { + path: '/edit-snippet/:id', + name: 'EditSnippet', + component: EditSnippet, + meta: { requiresAuth: true } + }, + { + path: '/tag/:tag', + name: 'TaggedSnippets', + component: TaggedSnippets + } +] + +const router = createRouter({ + history: createWebHistory(), + routes +}) + +router.beforeEach(async (to, from, next) => { + const authStore = useAuthStore() + + if (!authStore.hasCheckedAuth) { + await authStore.fetchUser() + } + + if (to.meta.requiresAuth && !authStore.isAuthenticated) { + next('/signin') + } else if (to.meta.requiresGuest && authStore.isAuthenticated) { + next('/account') + } else { + next() + } +}) + +export default router \ No newline at end of file diff --git a/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/stores/auth.js b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/stores/auth.js new file mode 100755 index 0000000..7c97eb9 --- /dev/null +++ b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/stores/auth.js @@ -0,0 +1,48 @@ +//client/src/stores/auth.js + +import { defineStore } from 'pinia' +import { ref, computed } from 'vue' +import { signin as apiSignin, signout as apiSignout, getProfile } from '../api' + +export const useAuthStore = defineStore('auth', () => { + const user = ref(null) + const isAuthenticated = computed(() => !!user.value) + const hasCheckedAuth = ref(false) + + const signin = async (credentials) => { + const response = await apiSignin(credentials) + user.value = response.data.user + hasCheckedAuth.value = true + return response.data.user + } + + const signout = async () => { + await apiSignout() + user.value = null + hasCheckedAuth.value = true + } + + const fetchUser = async () => { + if (hasCheckedAuth.value) return + + try { + const response = await getProfile() + user.value = response.data.user + } catch (error) { + user.value = null + } finally { + hasCheckedAuth.value = true + } + } + + return { + user, + isAuthenticated, + hasCheckedAuth, + signin, + signout, + fetchUser + } +}, { + persist: true +}) \ No newline at end of file diff --git a/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/stores/snippet.js b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/stores/snippet.js new file mode 100644 index 0000000..0ccfbab --- /dev/null +++ b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/stores/snippet.js @@ -0,0 +1,107 @@ +import { defineStore } from 'pinia' +import { ref } from 'vue' +import { + getPublicSnippets, + getUserSnippets, + createSnippet, + updateSnippet, + deleteSnippet, +} from '../api' + +export const useSnippetStore = defineStore( + 'snippet', + () => { + const snippets = ref([]) + const userSnippets = ref([]) + + // error handling helper + const withErrorHandling = (fn, errorMessage) => { + return async (...args) => { + try { + return await fn(...args) + } catch (error) { + console.error(`${errorMessage}`, error) + throw error + } + } + } + + const fetchPublicSnippets = withErrorHandling(async () => { + const { data } = await getPublicSnippets() + snippets.value = data.snippets + }, 'Error fetching public snippets:') + + const fetchUserSnippets = withErrorHandling(async () => { + const { data } = await getUserSnippets() + userSnippets.value = data.snippets + }, 'Error fetching user snippets:') + + const addSnippet = withErrorHandling(async (snippetData) => { + const { data } = await createSnippet(snippetData) + const newSnippet = data.snippet + userSnippets.value.push(newSnippet) + if (newSnippet.visibility) { + snippets.value.push(newSnippet) + } + }, 'Error adding snippet:') + + const editSnippet = withErrorHandling(async (id, snippetData) => { + const { data } = await updateSnippet(id, snippetData) + const updatedSnippet = data.snippet + + // Update userSnippets + const updateArray = (array) => { + const index = array.findIndex((s) => s.id === id) + if (index !== -1) { + array[index] = updatedSnippet + return true + } + return false + } + + updateArray(userSnippets.value) + + // Update public snippets + if (updatedSnippet.visibility) { + if (!updateArray(snippets.value)) { + snippets.value.push(updatedSnippet) + } + } else { + snippets.value = snippets.value.filter((s) => s.id !== id) + } + }, 'Error editing snippet:') + + const removeSnippet = withErrorHandling(async (id) => { + await deleteSnippet(id) + const removeFromArray = (array) => { + const index = array.findIndex((s) => s.id === id) + if (index !== -1) { + array.splice(index, 1) + } + } + removeFromArray(userSnippets.value) + removeFromArray(snippets.value) + }, 'Error removing snippet:') + + const clearSnippets = () => { + snippets.value = [] + userSnippets.value = [] + } + + return { + snippets, + userSnippets, + fetchPublicSnippets, + fetchUserSnippets, + addSnippet, + editSnippet, + removeSnippet, + clearSnippets, + } + }, + { + persist: { + paths: ['userSnippets'], + }, + } +) diff --git a/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/views/Account.vue b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/views/Account.vue new file mode 100644 index 0000000..b25d370 --- /dev/null +++ b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/views/Account.vue @@ -0,0 +1,43 @@ + + + diff --git a/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/views/AddSnippet.vue b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/views/AddSnippet.vue new file mode 100644 index 0000000..074215f --- /dev/null +++ b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/views/AddSnippet.vue @@ -0,0 +1,72 @@ + + + diff --git a/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/views/EditSnippet.vue b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/views/EditSnippet.vue new file mode 100644 index 0000000..e037e6d --- /dev/null +++ b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/views/EditSnippet.vue @@ -0,0 +1,70 @@ + + + \ No newline at end of file diff --git a/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/views/Home.vue b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/views/Home.vue new file mode 100755 index 0000000..33e67e3 --- /dev/null +++ b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/views/Home.vue @@ -0,0 +1,37 @@ + + + \ No newline at end of file diff --git a/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/views/Signin.vue b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/views/Signin.vue new file mode 100755 index 0000000..40ae87d --- /dev/null +++ b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/views/Signin.vue @@ -0,0 +1,84 @@ + + + + + \ No newline at end of file diff --git a/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/views/Signup.vue b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/views/Signup.vue new file mode 100755 index 0000000..4f8fd96 --- /dev/null +++ b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/views/Signup.vue @@ -0,0 +1,90 @@ + + + diff --git a/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/views/Snippets.vue b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/views/Snippets.vue new file mode 100644 index 0000000..fc2fd65 --- /dev/null +++ b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/views/Snippets.vue @@ -0,0 +1,51 @@ + + + diff --git a/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/views/TaggedSnippets.vue b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/views/TaggedSnippets.vue new file mode 100644 index 0000000..a7661e6 --- /dev/null +++ b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/src/views/TaggedSnippets.vue @@ -0,0 +1,47 @@ + + + \ No newline at end of file diff --git a/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/tailwind.config.js b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/tailwind.config.js new file mode 100755 index 0000000..12f8da6 --- /dev/null +++ b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/tailwind.config.js @@ -0,0 +1,11 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: [ + "./index.html", + "./src/**/*.{vue,js,ts,jsx,tsx}", + ], + theme: { + extend: {}, + }, + plugins: [], +} \ No newline at end of file diff --git a/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/vite.config.js b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/vite.config.js new file mode 100755 index 0000000..39e531d --- /dev/null +++ b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/client/vite.config.js @@ -0,0 +1,17 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import tailwindcss from 'tailwindcss' +import autoprefixer from 'autoprefixer' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [vue()], + css: { + postcss: { + plugins: [ + tailwindcss(), + autoprefixer(), + ], + }, + }, +}) \ No newline at end of file diff --git a/template/FullStack/Vue(Frontend)+GoFiber(Backend)/img/01.png b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/img/01.png new file mode 100644 index 0000000..9ed4404 Binary files /dev/null and b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/img/01.png differ diff --git a/template/FullStack/Vue(Frontend)+GoFiber(Backend)/img/02.png b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/img/02.png new file mode 100644 index 0000000..6300be9 Binary files /dev/null and b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/img/02.png differ diff --git a/template/FullStack/Vue(Frontend)+GoFiber(Backend)/img/03.png b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/img/03.png new file mode 100644 index 0000000..a9043f6 Binary files /dev/null and b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/img/03.png differ diff --git a/template/FullStack/Vue(Frontend)+GoFiber(Backend)/img/04.png b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/img/04.png new file mode 100644 index 0000000..95d5b18 Binary files /dev/null and b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/img/04.png differ diff --git a/template/FullStack/Vue(Frontend)+GoFiber(Backend)/img/05.png b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/img/05.png new file mode 100644 index 0000000..c58ecb9 Binary files /dev/null and b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/img/05.png differ diff --git a/template/FullStack/Vue(Frontend)+GoFiber(Backend)/server/.env.example b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/server/.env.example new file mode 100755 index 0000000..6da5749 --- /dev/null +++ b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/server/.env.example @@ -0,0 +1,9 @@ +POSTGRES_URL="postgresql://:@:/" +JWT_SECRET=password +PORT=3000 +ALLOWED_ORIGINS=http://localhost:5173 + +# Admin User +ADMIN_USERNAME=admin +ADMIN_EMAIL=admin@example.com +ADMIN_PASSWORD=password \ No newline at end of file diff --git a/template/FullStack/Vue(Frontend)+GoFiber(Backend)/server/config/config.go b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/server/config/config.go new file mode 100644 index 0000000..b460a1b --- /dev/null +++ b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/server/config/config.go @@ -0,0 +1,62 @@ +package config + +import ( + "log" + "os" + "sync" + + "github.com/joho/godotenv" +) + +var ( + config map[string]string + once sync.Once +) + +func Load() { + // Load configuration once, use env variables if available + once.Do(func() { + err := godotenv.Load() + if err != nil { + log.Println("No .env file found, using environment variables") + } + + // Populate configuration map + config = make(map[string]string) + config["PORT"] = getEnv("PORT", "3000") + config["POSTGRES_URL"] = getEnv("POSTGRES_URL", "") + config["JWT_SECRET"] = getEnv("JWT_SECRET", "") + config["ALLOWED_ORIGINS"] = getEnv("ALLOWED_ORIGINS", "http://localhost:5173") + config["ADMIN_USERNAME"] = getEnv("ADMIN_USERNAME", "admin") + config["ADMIN_EMAIL"] = getEnv("ADMIN_EMAIL", "admin@example.com") + config["ADMIN_PASSWORD"] = getEnv("ADMIN_PASSWORD", "") + + // Log loaded configuration + log.Println("Configuration loaded successfully") + log.Printf("PORT: %s", config["PORT"]) + log.Printf("POSTGRES_URL: %s", maskPassword(config["POSTGRES_URL"])) + log.Printf("ALLOWED_ORIGINS: %s", config["ALLOWED_ORIGINS"]) + log.Printf("ADMIN_USERNAME: %s", config["ADMIN_USERNAME"]) + log.Printf("ADMIN_EMAIL: %s", config["ADMIN_EMAIL"]) + }) +} + +func Get(key string) string { + // Retrieve a config value by key + return config[key] +} + +func getEnv(key, fallback string) string { + // Get environment variable or fallback if not set + if value, exists := os.LookupEnv(key); exists { + return value + } + if fallback == "" { + log.Fatalf("%s is not set in the environment", key) + } + return fallback +} + +func maskPassword(url string) string { + return "postgres://*****:*****@*****" +} diff --git a/template/FullStack/Vue(Frontend)+GoFiber(Backend)/server/controllers/auth_controller.go b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/server/controllers/auth_controller.go new file mode 100644 index 0000000..bc1a215 --- /dev/null +++ b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/server/controllers/auth_controller.go @@ -0,0 +1,168 @@ +package controllers + +import ( + "errors" + "server/config" + "server/middleware" + "server/models" + "time" + + "github.com/dgrijalva/jwt-go" + "github.com/gofiber/fiber/v2" + "golang.org/x/crypto/bcrypt" + "gorm.io/gorm" +) + +// Define input structs +type SignupInput struct { + Username string `json:"username"` + Email string `json:"emailid"` + Password string `json:"password"` +} + +type SigninInput struct { + Email string `json:"emailid"` + Password string `json:"password"` +} + +type AuthController struct{} + +// Factory function for creating new AuthController instances +func NewAuthController() *AuthController { + return &AuthController{} +} + +func (ac *AuthController) Signup(c *fiber.Ctx) error { + // Parse and validate user data for signup + var input SignupInput + if err := c.BodyParser(&input); err != nil { + return middleware.NewError(fiber.StatusBadRequest, "Cannot parse JSON") + } + + // Map input to User model + user := models.User{ + Username: input.Username, + Email: input.Email, + Password: input.Password, + } + + if err := user.Validate(); err != nil { + return middleware.NewError(fiber.StatusBadRequest, err.Error()) + } + + // Hash user password and create the user + if err := hashPassword(&user); err != nil { + return err + } + + if err := createUser(&user); err != nil { + return err + } + + return c.JSON(fiber.Map{"success": true, "message": "Signup successful!"}) +} + +func (ac *AuthController) Signin(c *fiber.Ctx) error { + // Parse credentials and authenticate user for signin + var credentials SigninInput + if err := c.BodyParser(&credentials); err != nil { + return middleware.NewError(fiber.StatusBadRequest, "Cannot parse JSON") + } + if credentials.Email == "" || credentials.Password == "" { + return middleware.NewError(fiber.StatusBadRequest, "Email and password are required") + } + + user, err := authenticateUser(credentials) + if err != nil { + return err + } + + // Generate JWT token and set it in the auth cookie + token, err := generateToken(user.ID) + if err != nil { + return err + } + + setAuthCookie(c, token) + + return c.JSON(fiber.Map{ + "success": true, + "user": formatUserResponse(user), + }) +} + +// Helper functions + +func hashPassword(user *models.User) error { + // Hash user password using bcrypt + hashedPassword, err := bcrypt.GenerateFromPassword([]byte(user.Password), bcrypt.DefaultCost) + if err != nil { + return middleware.NewError(fiber.StatusInternalServerError, "Error encrypting password") + } + user.Password = string(hashedPassword) + return nil +} + +func createUser(user *models.User) error { + // Create a new user record in the database + if err := models.DB.Create(user).Error; err != nil { + if err == gorm.ErrDuplicatedKey { + return middleware.NewError(fiber.StatusConflict, "User with this email or username already exists") + } + return middleware.NewError(fiber.StatusInternalServerError, "Error creating user") + } + return nil +} + +func authenticateUser(creds SigninInput) (models.User, error) { + // Authenticate user by email and password + var user models.User + if err := models.DB.Where("email = ?", creds.Email).First(&user).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return user, middleware.NewError(fiber.StatusUnauthorized, "Invalid email or password") + } + return user, middleware.NewError(fiber.StatusInternalServerError, "Error finding user") + } + + if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(creds.Password)); err != nil { + return user, middleware.NewError(fiber.StatusUnauthorized, "Invalid email or password") + } + + return user, nil +} + +func generateToken(userID uint) (string, error) { + // Generate JWT token with user ID as claim + token := jwt.New(jwt.SigningMethodHS256) + claims := token.Claims.(jwt.MapClaims) + claims["id"] = userID + claims["exp"] = time.Now().Add(time.Hour * 24).Unix() + + t, err := token.SignedString([]byte(config.Get("JWT_SECRET"))) + if err != nil { + return "", middleware.NewError(fiber.StatusInternalServerError, "Could not generate token") + } + return t, nil +} + +func setAuthCookie(c *fiber.Ctx, token string) { + // Set JWT token in a cookie for authentication + c.Cookie(&fiber.Cookie{ + Name: "access_token", + Value: token, + Expires: time.Now().Add(24 * time.Hour), + HTTPOnly: true, + Secure: false, // Set to true in production + SameSite: "strict", + Path: "/", + }) +} + +func formatUserResponse(user models.User) fiber.Map { + // Format user data for the response + return fiber.Map{ + "id": user.ID, + "username": user.Username, + "emailid": user.Email, + } +} diff --git a/template/FullStack/Vue(Frontend)+GoFiber(Backend)/server/controllers/snippet_controller.go b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/server/controllers/snippet_controller.go new file mode 100644 index 0000000..bec2c6e --- /dev/null +++ b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/server/controllers/snippet_controller.go @@ -0,0 +1,211 @@ +package controllers + +import ( + "server/middleware" + "server/models" + + "github.com/gofiber/fiber/v2" + "gorm.io/gorm" +) + +type SnippetController struct{} + +// creating a new SnippetController +func NewSnippetController() *SnippetController { + return &SnippetController{} +} + +func (sc *SnippetController) GetSnippets(c *fiber.Ctx) error { + // Get all visible snippets with associated users + snippets, err := fetchSnippets(models.DB.Where("visibility = ?", true).Preload("User")) + if err != nil { + return handleDatabaseError(err, "Error fetching snippets") + } + return c.JSON(fiber.Map{"success": true, "snippets": snippets}) +} + +func (sc *SnippetController) GetUserSnippets(c *fiber.Ctx) error { + // Get snippets for the current authenticated user + userID := extractUserID(c) + snippets, err := fetchSnippets(models.DB.Where("user_id = ?", userID).Preload("User")) + if err != nil { + return handleDatabaseError(err, "Error fetching user snippets") + } + return c.JSON(fiber.Map{"success": true, "snippets": snippets}) +} + +func (sc *SnippetController) GetSnippet(c *fiber.Ctx) error { + // Get a single snippet by ID + id := c.Params("id") + snippet, err := fetchSingleSnippet(id) + if err != nil { + return err + } + + if !snippet.Visibility && !isAuthorized(c, snippet.UserID) { + return middleware.NewError(fiber.StatusForbidden, "You don't have permission to view this snippet") + } + return c.JSON(fiber.Map{"success": true, "snippet": snippet}) +} + +func (sc *SnippetController) CreateSnippet(c *fiber.Ctx) error { + // Create new snippet for the current authenticated user + input, err := parseSnippetInput(c) + if err != nil { + return err + } + + userID := extractUserID(c) + if err := validateUser(userID); err != nil { + return err + } + + snippet := createSnippetFromInput(input, userID) + if err := models.DB.Create(&snippet).Error; err != nil { + return handleDatabaseError(err, "Error creating snippet") + } + // Preload User data + if err := models.DB.Preload("User").First(&snippet, snippet.ID).Error; err != nil { + return handleDatabaseError(err, "Error fetching snippet with user data") + } + return c.JSON(fiber.Map{"success": true, "snippet": snippet}) +} + +func (sc *SnippetController) UpdateSnippet(c *fiber.Ctx) error { + // Update an existing snippet + id := c.Params("id") + userID := extractUserID(c) + + existingSnippet, err := fetchSingleSnippet(id) + if err != nil { + return err + } + + if existingSnippet.UserID != userID { + return middleware.NewError(fiber.StatusForbidden, "You don't have permission to update this snippet") + } + + input, err := parseSnippetInput(c) + if err != nil { + return err + } + + updateSnippetFromInput(&existingSnippet, input) + if err := models.DB.Save(&existingSnippet).Error; err != nil { + return handleDatabaseError(err, "Error updating snippet") + } + // Preload User data + if err := models.DB.Preload("User").First(&existingSnippet, existingSnippet.ID).Error; err != nil { + return handleDatabaseError(err, "Error fetching snippet with user data") + } + + return c.JSON(fiber.Map{"success": true, "snippet": existingSnippet}) +} + +func (sc *SnippetController) DeleteSnippet(c *fiber.Ctx) error { + // Delete a snippet + id := c.Params("id") + userID := extractUserID(c) + + snippet, err := fetchSingleSnippet(id) + if err != nil { + return err + } + + if snippet.UserID != userID { + return middleware.NewError(fiber.StatusForbidden, "You don't have permission to delete this snippet") + } + + if err := models.DB.Delete(&snippet).Error; err != nil { + return handleDatabaseError(err, "Error deleting snippet") + } + return c.JSON(fiber.Map{"success": true, "message": "Snippet deleted successfully"}) +} + +func (sc *SnippetController) GetLanguages(c *fiber.Ctx) error { + // Retrieve all languages + var languages []models.Language + if err := models.DB.Find(&languages).Error; err != nil { + return handleDatabaseError(err, "Error fetching languages") + } + return c.JSON(fiber.Map{"success": true, "languages": languages}) +} + +// Helper functions + +func fetchSnippets(query *gorm.DB) ([]models.Snippet, error) { + // Fetch a list of snippets + var snippets []models.Snippet + err := query.Find(&snippets).Error + return snippets, err +} + +func fetchSingleSnippet(id string) (models.Snippet, error) { + // Fetch a single snippet by ID + var snippet models.Snippet + err := models.DB.Preload("User").First(&snippet, id).Error + if err == gorm.ErrRecordNotFound { + return snippet, middleware.NewError(fiber.StatusNotFound, "Snippet not found") + } + return snippet, err +} + +func extractUserID(c *fiber.Ctx) uint { + // Extract the authenticated user ID from the context + return c.Locals("userId").(uint) +} + +func isAuthorized(c *fiber.Ctx, snippetUserID uint) bool { + // Check if the current user is authorized to access the snippet + userID, ok := c.Locals("userId").(uint) + return ok && snippetUserID == userID +} + +func validateUser(userID uint) error { + // Validate if the user exists in the database + var user models.User + if err := models.DB.First(&user, userID).Error; err != nil { + if err == gorm.ErrRecordNotFound { + return middleware.NewError(fiber.StatusBadRequest, "Invalid user ID") + } + return handleDatabaseError(err, "Error checking user existence") + } + return nil +} + +func parseSnippetInput(c *fiber.Ctx) (models.SnippetInput, error) { + // Parse snippet input from the request body + var input models.SnippetInput + if err := c.BodyParser(&input); err != nil { + return input, middleware.NewError(fiber.StatusBadRequest, "Invalid input") + } + return input, nil +} + +func createSnippetFromInput(input models.SnippetInput, userID uint) models.Snippet { + // Create a Snippet object from the input and user ID + return models.Snippet{ + Name: input.Name, + Description: input.Description, + Code: input.Code, + Language: input.Language, + Tags: input.Tags, + Visibility: input.Visibility, + UserID: userID, + } +} + +func updateSnippetFromInput(snippet *models.Snippet, input models.SnippetInput) { + // Update the snippet fields based on the input + snippet.Name = input.Name + snippet.Description = input.Description + snippet.Code = input.Code + snippet.Language = input.Language + snippet.Tags = input.Tags + snippet.Visibility = input.Visibility +} + +func handleDatabaseError(err error, message string) error { + // Return a generic error message for database errors + return middleware.NewError(fiber.StatusInternalServerError, message) +} diff --git a/template/FullStack/Vue(Frontend)+GoFiber(Backend)/server/controllers/user_controller.go b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/server/controllers/user_controller.go new file mode 100644 index 0000000..29738c0 --- /dev/null +++ b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/server/controllers/user_controller.go @@ -0,0 +1,53 @@ +package controllers + +import ( + "server/models" + "time" + + "github.com/gofiber/fiber/v2" +) + +type UserController struct{} + +func NewUserController() *UserController { + return &UserController{} +} + +func (uc *UserController) Test(c *fiber.Ctx) error { + // API status check + return c.JSON(fiber.Map{"message": "API is working!"}) +} + +func (uc *UserController) Signout(c *fiber.Ctx) error { + // Clear the user session by removing the access token + c.Cookie(&fiber.Cookie{ + Name: "access_token", + Value: "", + Expires: time.Now().Add(-time.Hour), + HTTPOnly: true, + Secure: false, + SameSite: "strict", + Path: "/", + }) + return c.JSON(fiber.Map{"success": true, "message": "User has been signed out!"}) +} + +func (uc *UserController) GetProfile(c *fiber.Ctx) error { + // Fetch the current user's profile details + userID := c.Locals("userId").(uint) + + var user models.User + if err := models.DB.First(&user, userID).Error; err != nil { + return fiber.NewError(fiber.StatusNotFound, "User not found") + } + + // Return user profile information + return c.JSON(fiber.Map{ + "success": true, + "user": fiber.Map{ + "id": user.ID, + "username": user.Username, + "emailid": user.Email, + }, + }) +} diff --git a/template/FullStack/Vue(Frontend)+GoFiber(Backend)/server/data/languages.json b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/server/data/languages.json new file mode 100644 index 0000000..45b70c2 --- /dev/null +++ b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/server/data/languages.json @@ -0,0 +1,30 @@ +{ + "languages": [ + "Bash", + "Clojure", + "C", + "CSS", + "Elixir", + "Go", + "Haskell", + "Java", + "JavaScript", + "TypeScript", + "JSON", + "JSX", + "Julia", + "Kotlin", + "HTML", + "Nim", + "PHP", + "Python", + "Ruby", + "Rust", + "Sass", + "SCSS", + "SQL", + "TOML", + "YAML", + "Zig" + ] +} diff --git a/template/FullStack/Vue(Frontend)+GoFiber(Backend)/server/data/snippets.yaml b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/server/data/snippets.yaml new file mode 100644 index 0000000..667893a --- /dev/null +++ b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/server/data/snippets.yaml @@ -0,0 +1,35 @@ +snippets: + - name: "Fibonacci in JavaScript" + description: "Generate Fibonacci sequence" + code: | + function fibonacci(n) { + if (n <= 1) return n; + return fibonacci(n - 1) + fibonacci(n - 2); + } + language: "JavaScript" + tags: "recursion" + visibility: true + + - name: "Fibonacci in Python" + description: "Generate Fibonacci sequence" + code: | + def fibonacci(n): + if n <= 1: + return n + return fibonacci(n-1) + fibonacci(n-2) + language: "Python" + tags: "recursion" + visibility: true + + - name: "Fibonacci in Go" + description: "Generate Fibonacci sequence" + code: | + func fibonacci(n int) int { + if n <= 1 { + return n + } + return fibonacci(n-1) + fibonacci(n-2) + } + language: "Go" + tags: "recursion" + visibility: true \ No newline at end of file diff --git a/template/FullStack/Vue(Frontend)+GoFiber(Backend)/server/go.mod b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/server/go.mod new file mode 100644 index 0000000..108f011 --- /dev/null +++ b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/server/go.mod @@ -0,0 +1,33 @@ +module server + +go 1.18 + +require ( + github.com/dgrijalva/jwt-go v3.2.0+incompatible + github.com/gofiber/fiber/v2 v2.52.5 + github.com/joho/godotenv v1.5.1 + golang.org/x/crypto v0.14.0 + gopkg.in/yaml.v2 v2.4.0 + gorm.io/driver/postgres v1.5.0 + gorm.io/gorm v1.24.7-0.20230306060331-85eaf9eeda11 +) + +require ( + github.com/andybalholm/brotli v1.0.5 // indirect + github.com/google/uuid v1.5.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/jackc/pgx/v5 v5.3.0 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/klauspost/compress v1.17.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/rivo/uniseg v0.2.0 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasthttp v1.51.0 // indirect + github.com/valyala/tcplisten v1.0.0 // indirect + golang.org/x/sys v0.15.0 // indirect + golang.org/x/text v0.13.0 // indirect +) diff --git a/template/FullStack/Vue(Frontend)+GoFiber(Backend)/server/main.go b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/server/main.go new file mode 100644 index 0000000..4fb0167 --- /dev/null +++ b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/server/main.go @@ -0,0 +1,37 @@ +package main + +import ( + "log" + "server/config" + "server/middleware" + "server/models" + "server/routes" + + "github.com/gofiber/fiber/v2" +) + +func main() { + // Load app configuration + config.Load() + + // Connect to database + if err := models.ConnectDB(); err != nil { + log.Fatalf("Failed to connect to database: %v", err) + } + + // Create new Fiber instance with custom error handler + app := fiber.New(fiber.Config{ + ErrorHandler: middleware.ErrorHandler, + }) + + // Middleware setup (logging, CORS) + app.Use(middleware.Logger()) + app.Use(middleware.Cors()) + + // Setup routes + routes.SetupRoutes(app) + + // Start server on configured port + port := config.Get("PORT") + log.Fatal(app.Listen(":" + port)) +} diff --git a/template/FullStack/Vue(Frontend)+GoFiber(Backend)/server/middleware/auth_middleware.go b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/server/middleware/auth_middleware.go new file mode 100644 index 0000000..f3fee82 --- /dev/null +++ b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/server/middleware/auth_middleware.go @@ -0,0 +1,43 @@ +package middleware + +import ( + "server/config" + + "github.com/dgrijalva/jwt-go" + "github.com/gofiber/fiber/v2" +) + +// Verify JWT token and sets user ID +func VerifyToken(c *fiber.Ctx) error { + // Extract token from cookie + tokenString := c.Cookies("access_token") + if tokenString == "" { + return fiber.NewError(fiber.StatusUnauthorized, "Unauthorized") + } + + // Parse and validate the token + token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { + // Verify signing method + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fiber.NewError(fiber.StatusUnauthorized, "Unexpected signing method") + } + return []byte(config.Get("JWT_SECRET")), nil + }) + + if err != nil { + return fiber.NewError(fiber.StatusUnauthorized, "Invalid Token") + } + + // Extract claims and set user ID + if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { + userIDFloat, ok := claims["id"].(float64) + if !ok { + return fiber.NewError(fiber.StatusUnauthorized, "Invalid Token") + } + userID := uint(userIDFloat) + c.Locals("userId", userID) + return c.Next() + } + + return fiber.NewError(fiber.StatusUnauthorized, "Invalid Token") +} diff --git a/template/FullStack/Vue(Frontend)+GoFiber(Backend)/server/middleware/cors_middleware.go b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/server/middleware/cors_middleware.go new file mode 100644 index 0000000..360f525 --- /dev/null +++ b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/server/middleware/cors_middleware.go @@ -0,0 +1,36 @@ +package middleware + +import ( + "server/config" + "strings" + + "github.com/gofiber/fiber/v2" +) + +func Cors() fiber.Handler { + return func(c *fiber.Ctx) error { + // Get request origin and allowed origins from config + origin := c.Get("Origin") + allowedOrigins := strings.Split(config.Get("ALLOWED_ORIGINS"), ",") + + // Set CORS headers if the origin is allowed + for _, allowedOrigin := range allowedOrigins { + if origin == allowedOrigin { + c.Set("Access-Control-Allow-Origin", origin) + break + } + } + + // Set other CORS headers + c.Set("Access-Control-Allow-Credentials", "true") + c.Set("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS,PATCH") + c.Set("Access-Control-Allow-Headers", "Content-Type,Authorization,X-Requested-With") + + // Handle preflight OPTIONS request + if c.Method() == fiber.MethodOptions { + return c.SendStatus(fiber.StatusNoContent) + } + + return c.Next() + } +} diff --git a/template/FullStack/Vue(Frontend)+GoFiber(Backend)/server/middleware/error_middleware.go b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/server/middleware/error_middleware.go new file mode 100644 index 0000000..a46b65e --- /dev/null +++ b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/server/middleware/error_middleware.go @@ -0,0 +1,71 @@ +package middleware + +import ( + "github.com/gofiber/fiber/v2" +) + +// AppError is a custom error type for application-specific errors +type AppError struct { + Code int `json:"code"` + Message string `json:"message"` +} + +func (e AppError) Error() string { + return e.Message +} + +// ErrorHandler is a global error handler middleware +func ErrorHandler(c *fiber.Ctx, err error) error { + // Default to 500 Internal Server Error + code := fiber.StatusInternalServerError + message := "Internal Server Error" + + // Check if it's our custom AppError + if e, ok := err.(AppError); ok { + code = e.Code + message = e.Message + } else if e, ok := err.(*fiber.Error); ok { + // It's a Fiber error + code = e.Code + message = e.Message + } + + // Send JSON response + return c.Status(code).JSON(fiber.Map{ + "success": false, + "error": fiber.Map{ + "code": code, + "message": message, + }, + }) +} + +// NewError creates a new AppError +func NewError(code int, message string) AppError { + // Return a new instance of AppError with the provided code and message + return AppError{ + Code: code, + Message: message, + } +} + +// ErrorResponse sends a JSON error with custom code and message +func ErrorResponse(c *fiber.Ctx, code int, message string) error { + // Send a JSON response with the provided code and message + return c.Status(code).JSON(fiber.Map{ + "success": false, + "error": fiber.Map{ + "code": code, + "message": message, + }, + }) +} + +// Logger middleware for logging requests +func Logger() fiber.Handler { + // Middleware function that logs requests + return func(c *fiber.Ctx) error { + // Continue to the next middleware/handler + return c.Next() + } +} diff --git a/template/FullStack/Vue(Frontend)+GoFiber(Backend)/server/models/db.go b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/server/models/db.go new file mode 100644 index 0000000..7e6106b --- /dev/null +++ b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/server/models/db.go @@ -0,0 +1,235 @@ +package models + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "log" + "os" + "server/config" + + "golang.org/x/crypto/bcrypt" + "gopkg.in/yaml.v2" + "gorm.io/driver/postgres" + "gorm.io/gorm" +) + +var DB *gorm.DB + +type LanguagesData struct { + Languages []string `json:"languages"` +} + +type SnippetsData struct { + Snippets []Snippet `yaml:"snippets"` +} + +func ConnectDB() error { + // Initialize DB connection and migrate schema + db, err := initializeDatabase() + if err != nil { + return err + } + + if err := migrateSchema(db); err != nil { + return err + } + + DB = db + log.Println("Connected to PostgreSQL and migrated schema") + + // Seed initial data + if err := seedData(); err != nil { + return err + } + + return nil +} + +func initializeDatabase() (*gorm.DB, error) { + // Set up PostgreSQL connection + dsn := config.Get("POSTGRES_URL") + db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{}) + if err != nil { + return nil, fmt.Errorf("failed to connect to database: %w", err) + } + return db, nil +} + +func migrateSchema(db *gorm.DB) error { + // Auto-migrate the schema for User, Snippet, and Language models + if err := db.AutoMigrate(&User{}, &Snippet{}, &Language{}); err != nil { + return fmt.Errorf("failed to migrate database: %w", err) + } + return nil +} + +func seedData() error { + // Seed languages, admin user, and snippets + if err := seedLanguages(); err != nil { + return fmt.Errorf("failed to seed languages: %w", err) + } + + if err := seedAdminUser(); err != nil { + return fmt.Errorf("failed to seed admin user: %w", err) + } + + if err := seedSnippets(); err != nil { + return fmt.Errorf("failed to seed snippets: %w", err) + } + + return nil +} + +func seedLanguages() error { + // Seed language data if not already present + if hasExistingData(&Language{}) { + log.Println("Languages already exist in the database. Skipping seeding.") + return nil + } + + languages, err := loadLanguagesFromFile() + if err != nil { + return err + } + + for _, lang := range languages { + if err := createLanguage(lang); err != nil { + log.Printf("Error creating language %s: %v", lang, err) + } else { + log.Printf("Successfully added language: %s", lang) + } + } + + log.Println("Languages have been seeded successfully") + return nil +} + +func seedAdminUser() error { + // Seed admin user if no users exist + if hasExistingData(&User{}) { + log.Println("Users already exist in the database. Skipping admin user creation.") + return nil + } + + adminUser, err := createAdminUser() + if err != nil { + return fmt.Errorf("failed to create admin user: %w", err) + } + + if err := DB.Create(adminUser).Error; err != nil { + return fmt.Errorf("failed to save admin user: %w", err) + } + + log.Println("Admin user created successfully") + return nil +} + +func seedSnippets() error { + // Seed snippets if no snippets exist + if hasExistingData(&Snippet{}) { + log.Println("Snippets already exist in the database. Skipping seeding.") + return nil + } + + snippets, err := loadSnippetsFromFile() + if err != nil { + return err + } + + adminUser, err := findAdminUser() + if err != nil { + return err + } + + for _, snippet := range snippets { + if err := createSnippet(&snippet, adminUser.ID); err != nil { + log.Printf("Error creating snippet %s: %v", snippet.Name, err) + } else { + log.Printf("Successfully added snippet: %s", snippet.Name) + } + } + + log.Println("Sample snippets have been seeded successfully") + return nil +} + +// Helper functions + +func hasExistingData(model interface{}) bool { + // Check if data already exists in the specified model + var count int64 + DB.Model(model).Count(&count) + return count > 0 +} + +func loadLanguagesFromFile() ([]string, error) { + // Load languages from JSON file + jsonFile, err := os.Open("data/languages.json") + if err != nil { + return nil, fmt.Errorf("failed to open languages file: %w", err) + } + defer jsonFile.Close() + + var languagesData LanguagesData + byteValue, _ := ioutil.ReadAll(jsonFile) + if err := json.Unmarshal(byteValue, &languagesData); err != nil { + return nil, fmt.Errorf("failed to unmarshal languages data: %w", err) + } + + return languagesData.Languages, nil +} + +func createLanguage(name string) error { + // Create new language entry + return DB.Create(&Language{Name: name}).Error +} + +func createAdminUser() (*User, error) { + // Create admin user with credentials from configuration + adminUsername := config.Get("ADMIN_USERNAME") + adminEmail := config.Get("ADMIN_EMAIL") + adminPassword := config.Get("ADMIN_PASSWORD") + + hashedPassword, err := bcrypt.GenerateFromPassword([]byte(adminPassword), bcrypt.DefaultCost) + if err != nil { + return nil, fmt.Errorf("failed to hash admin password: %w", err) + } + + return &User{ + Username: adminUsername, + Email: adminEmail, + Password: string(hashedPassword), + }, nil +} + +func loadSnippetsFromFile() ([]Snippet, error) { + // Load snippets from YAML file + yamlFile, err := ioutil.ReadFile("data/snippets.yaml") + if err != nil { + return nil, fmt.Errorf("failed to read snippets file: %w", err) + } + + var snippetsData SnippetsData + if err := yaml.Unmarshal(yamlFile, &snippetsData); err != nil { + return nil, fmt.Errorf("failed to unmarshal snippets data: %w", err) + } + + return snippetsData.Snippets, nil +} + +func findAdminUser() (User, error) { + // Find admin user by username + var adminUser User + if err := DB.Where("username = ?", config.Get("ADMIN_USERNAME")).First(&adminUser).Error; err != nil { + return adminUser, fmt.Errorf("failed to find admin user: %w", err) + } + return adminUser, nil +} + +func createSnippet(snippet *Snippet, adminUserID uint) error { + // Create new snippet and associate with admin user + snippet.UserID = adminUserID + snippet.Visibility = true // Ensure all initial snippets are public + return DB.Create(snippet).Error +} diff --git a/template/FullStack/Vue(Frontend)+GoFiber(Backend)/server/models/language.go b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/server/models/language.go new file mode 100644 index 0000000..cd4ad12 --- /dev/null +++ b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/server/models/language.go @@ -0,0 +1,10 @@ +package models + +import ( + "gorm.io/gorm" +) + +type Language struct { + gorm.Model + Name string `json:"name" gorm:"unique;not null"` +} diff --git a/template/FullStack/Vue(Frontend)+GoFiber(Backend)/server/models/snippet.go b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/server/models/snippet.go new file mode 100644 index 0000000..be14274 --- /dev/null +++ b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/server/models/snippet.go @@ -0,0 +1,32 @@ +package models + +import ( + "time" + + "gorm.io/gorm" +) + +type Snippet struct { + ID uint `gorm:"primarykey" json:"id"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` + Name string `json:"name" gorm:"not null"` + Description string `json:"description"` + Code string `json:"code" gorm:"not null"` + Language string `json:"language" gorm:"not null"` + Tags string `json:"tags"` + Visibility bool `json:"visibility" gorm:"not null;default:true"` + UserID uint `json:"userId" gorm:"not null"` + User User `json:"user" gorm:"foreignKey:UserID"` +} + +type SnippetInput struct { + // Struct used for creating/updating snippets + Name string `json:"name"` + Description string `json:"description"` + Code string `json:"code"` + Language string `json:"language"` + Tags string `json:"tags"` + Visibility bool `json:"visibility"` +} diff --git a/template/FullStack/Vue(Frontend)+GoFiber(Backend)/server/models/user.go b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/server/models/user.go new file mode 100644 index 0000000..e5e4b17 --- /dev/null +++ b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/server/models/user.go @@ -0,0 +1,44 @@ +package models + +import ( + "errors" + "regexp" + "time" + + "gorm.io/gorm" +) + +type User struct { + // User fields + ID uint `gorm:"primarykey" json:"id"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` // Soft delete field + Username string `gorm:"uniqueIndex;not null" json:"username"` + Email string `gorm:"uniqueIndex;not null" json:"emailid"` + Password string `gorm:"not null" json:"-"` // Exclude password from JSON + Snippets []Snippet `gorm:"foreignKey:UserID" json:"snippets,omitempty"` // Relationship to Snippets +} + +func (u *User) Validate() error { + // Validate mandatory fields and email format + if u.Username == "" { + return errors.New("username is required") + } + if u.Email == "" { + return errors.New("email is required") + } + if u.Password == "" { + return errors.New("password is required") + } + if !isValidEmail(u.Email) { + return errors.New("invalid email format") + } + return nil +} + +func isValidEmail(email string) bool { + // Basic email format validation + emailRegex := regexp.MustCompile(`\S+@\S+\.\S+`) + return emailRegex.MatchString(email) +} diff --git a/template/FullStack/Vue(Frontend)+GoFiber(Backend)/server/routes/auth_routes.go b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/server/routes/auth_routes.go new file mode 100644 index 0000000..b98774d --- /dev/null +++ b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/server/routes/auth_routes.go @@ -0,0 +1,17 @@ +package routes + +import ( + "server/controllers" + + "github.com/gofiber/fiber/v2" +) + +func SetupAuthRoutes(router fiber.Router) { + // Authentication endpoints + auth := router.Group("/auth") + authController := controllers.NewAuthController() + + // Authentication routes + auth.Post("/signup", authController.Signup) + auth.Post("/signin", authController.Signin) +} diff --git a/template/FullStack/Vue(Frontend)+GoFiber(Backend)/server/routes/router.go b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/server/routes/router.go new file mode 100644 index 0000000..f5adec9 --- /dev/null +++ b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/server/routes/router.go @@ -0,0 +1,13 @@ +package routes + +import ( + "github.com/gofiber/fiber/v2" +) + +func SetupRoutes(app *fiber.App) { + api := app.Group("/api") + + SetupAuthRoutes(api) + SetupUserRoutes(api) + SetupSnippetRoutes(api) +} diff --git a/template/FullStack/Vue(Frontend)+GoFiber(Backend)/server/routes/snippet_routes.go b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/server/routes/snippet_routes.go new file mode 100644 index 0000000..d27818b --- /dev/null +++ b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/server/routes/snippet_routes.go @@ -0,0 +1,25 @@ +package routes + +import ( + "server/controllers" + "server/middleware" + + "github.com/gofiber/fiber/v2" +) + +func SetupSnippetRoutes(router fiber.Router) { + snippet := router.Group("/snippets") + snippetController := controllers.NewSnippetController() + + // Public routes + snippet.Get("/", snippetController.GetSnippets) + snippet.Get("/languages", snippetController.GetLanguages) + + // Protected routes + snippet.Use(middleware.VerifyToken) + snippet.Get("/user", snippetController.GetUserSnippets) + snippet.Get("/:id", snippetController.GetSnippet) + snippet.Post("/", snippetController.CreateSnippet) + snippet.Put("/:id", snippetController.UpdateSnippet) + snippet.Delete("/:id", snippetController.DeleteSnippet) +} diff --git a/template/FullStack/Vue(Frontend)+GoFiber(Backend)/server/routes/user_routes.go b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/server/routes/user_routes.go new file mode 100644 index 0000000..ca3fcad --- /dev/null +++ b/template/FullStack/Vue(Frontend)+GoFiber(Backend)/server/routes/user_routes.go @@ -0,0 +1,21 @@ +package routes + +import ( + "server/controllers" + "server/middleware" + + "github.com/gofiber/fiber/v2" +) + +func SetupUserRoutes(router fiber.Router) { + // configures user endpoints + user := router.Group("/user") + userController := controllers.NewUserController() + + // Public + user.Get("/test", userController.Test) + user.Post("/signout", userController.Signout) + + // Protected + user.Get("/profile", middleware.VerifyToken, userController.GetProfile) +} diff --git a/website/content/Templates/FullStack/3.Vue(Frontend)+GoFiber(Backend).md b/website/content/Templates/FullStack/3.Vue(Frontend)+GoFiber(Backend).md new file mode 100644 index 0000000..217f171 --- /dev/null +++ b/website/content/Templates/FullStack/3.Vue(Frontend)+GoFiber(Backend).md @@ -0,0 +1,37 @@ +## Introduction + +The **FullStack Template: Vue.js + Go Fiber** is a starter template for building web applications. It provides a responsive frontend using **Vue.js** and a backend powered by **Go Fiber**.The template includes essential features such as: CRUD operations, user authentication, session management, and a responsive user interface. + +## Features + +- **User Authentication**: Registration, login, and logout functionality with JWT-based session management. +- **Snippet Management**: Users can create, read, update, and delete code snippets with syntax highlighting and tagging. +- **Public and Private Snippets**: Snippets can be marked as public or private. +- **Syntax highlighting**: Syntax highlighting for multiple programming languages. +- **Tagging**: Snippets can be tagged, with support for browsing by tags. +- **Responsive Design**: Tailwind CSS for a modern and adaptable user interface. + + +## Technologies Used + +### Frontend + +- **Vue.js 3** +- **Tailwind CSS** +- **Vue Router** +- **Pinia** +- **Vue Toastification** +- **Prism.js** + +### Backend + +- **Go Fiber** +- **PostgreSQL** +- **JWT (dgrijalva/jwt-go)** +- **GORM** +- **bcrypt** +- **godotenv** + +--- +Visit the codebase [here](https://github.com/Abhishek-Mallick/universal-box/tree/main/template/FullStack/Vue(Frontend)+GoFiber(Backend)) +---