chore: init adonis

This commit is contained in:
Sonny
2024-04-27 15:49:47 +02:00
committed by Sonny
parent 219e5e3aed
commit 1386db6935
259 changed files with 7184 additions and 18173 deletions

View File

@@ -1,13 +0,0 @@
node_modules
.env
.next
.vscode
example.env
# Additional good to have ignores for dockerignore
Dockerfile*
docker-compose*
.dockerignore
*.md
.git
.gitignore

View File

@@ -1,10 +1,22 @@
root = true
# http://editorconfig.org
[*]
charset = utf-8
indent_style = space
indent_size = 2
tab_width = 4
end_of_line = lf
insert_final_newline = true
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.json]
insert_final_newline = unset
[**.min.js]
indent_style = unset
insert_final_newline = unset
[MakeFile]
indent_style = space
[*.md]
trim_trailing_whitespace = false

12
.env.example Normal file
View File

@@ -0,0 +1,12 @@
TZ=UTC
PORT=3333
HOST=localhost
LOG_LEVEL=info
APP_KEY=sLoJth45JD1vcS8n92Y2JUd8w3OL4HQb
NODE_ENV=development
SESSION_DRIVER=cookie
DB_HOST=127.0.0.1
DB_PORT=5432
DB_USER=postgres
DB_PASSWORD=
DB_DATABASE=

View File

@@ -1,6 +0,0 @@
{
"extends": "next",
"rules": {
"react/no-unescaped-entities": "off"
}
}

2
.gitattributes vendored
View File

@@ -1,2 +0,0 @@
# Auto detect text files and perform LF normalization
* text=auto

View File

@@ -1,53 +0,0 @@
name: CI/CD prod
on:
release:
types: published
jobs:
push_image_to_docker_hub:
name: Build and push image to Docker Hub
runs-on: ubuntu-latest
steps:
- name: Checkout the repo
uses: actions/checkout@v4
- name: Docker meta
id: docker_meta
uses: docker/metadata-action@v5.5.1
with:
images: sonny93/my-links
flavor: latest=true
tags: |
type=semver,pattern={{version}}
- name: Login to DockerHub
uses: docker/login-action@v3.1.0
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push
uses: docker/build-push-action@v5.3.0
with:
file: ./Dockerfile
context: .
push: true
tags: ${{ steps.docker_meta.outputs.tags }}
labels: ${{ steps.docker_meta.outputs.labels }}
execute_commands_via_ssh:
name: Pull latest docker image and start up the application with Docker Compose
runs-on: ubuntu-latest
needs: push_image_to_docker_hub
steps:
- name: Executing remote ssh commands
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SSH_HOST }}
port: ${{ secrets.SSH_PORT }}
username: ${{ secrets.SSH_USERNAME }}
key: ${{ secrets.SSH_KEY }}
script: |
cd /infra/my-links
sh startup.sh

56
.gitignore vendored
View File

@@ -1,45 +1,25 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# Dependencies and AdonisJS build
node_modules
build
tmp
# dependencies
/node_modules
/.pnp
.pnp.js
# Secrets
.env
.env.local
.env.production.local
.env.development.local
# testing
/coverage
# Frontend assets compiled code
public/assets
# next.js
/.next/
/out/
# Build tools specific
npm-debug.log
yarn-error.log
# production
/build
# misc
.DS_Store
*.pem
# Editors specific
.fleet
.idea
.vscode
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
# vercel
.vercel
# sitemap files (they are generated by postbuild script)
public/sitemap*
public/robots.txt
# pwa static files
/public/sw*
/public/workbox-*
# Platform specific
.DS_Store

1
.husky/.gitignore vendored
View File

@@ -1 +0,0 @@
_

View File

@@ -1,4 +0,0 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx lint-staged

8
.idea/.gitignore generated vendored
View File

@@ -1,8 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View File

@@ -1,63 +0,0 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<HTMLCodeStyleSettings>
<option name="HTML_SPACE_INSIDE_EMPTY_TAG" value="true" />
<option name="HTML_QUOTE_STYLE" value="Single" />
<option name="HTML_ENFORCE_QUOTES" value="true" />
</HTMLCodeStyleSettings>
<JSCodeStyleSettings version="0">
<option name="FORCE_SEMICOLON_STYLE" value="true" />
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
<option name="USE_DOUBLE_QUOTES" value="false" />
<option name="FORCE_QUOTE_STYlE" value="true" />
<option name="ENFORCE_TRAILING_COMMA" value="WhenMultiline" />
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
<option name="SPACES_WITHIN_IMPORTS" value="true" />
</JSCodeStyleSettings>
<TypeScriptCodeStyleSettings version="0">
<option name="FORCE_SEMICOLON_STYLE" value="true" />
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
<option name="USE_DOUBLE_QUOTES" value="false" />
<option name="FORCE_QUOTE_STYlE" value="true" />
<option name="ENFORCE_TRAILING_COMMA" value="WhenMultiline" />
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
<option name="SPACES_WITHIN_IMPORTS" value="true" />
</TypeScriptCodeStyleSettings>
<VueCodeStyleSettings>
<option name="INTERPOLATION_NEW_LINE_AFTER_START_DELIMITER" value="false" />
<option name="INTERPOLATION_NEW_LINE_BEFORE_END_DELIMITER" value="false" />
</VueCodeStyleSettings>
<codeStyleSettings language="HTML">
<option name="SOFT_MARGINS" value="100" />
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="CONTINUATION_INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="2" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="JavaScript">
<option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
<option name="SOFT_MARGINS" value="100" />
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="CONTINUATION_INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="2" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="TypeScript">
<option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
<option name="SOFT_MARGINS" value="100" />
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="CONTINUATION_INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="2" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="Vue">
<option name="SOFT_MARGINS" value="100" />
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="2" />
</indentOptions>
</codeStyleSettings>
</code_scheme>
</component>

View File

@@ -1,5 +0,0 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>

5
.idea/misc.xml generated
View File

@@ -1,5 +0,0 @@
<project version="4">
<component name="ProjectRootManager">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

8
.idea/modules.xml generated
View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/my-links.iml" filepath="$PROJECT_DIR$/.idea/my-links.iml" />
</modules>
</component>
</project>

9
.idea/my-links.iml generated
View File

@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

7
.idea/prettier.xml generated
View File

@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="PrettierConfiguration">
<option name="myConfigurationMode" value="AUTOMATIC" />
<option name="myRunOnSave" value="true" />
</component>
</project>

6
.idea/vcs.xml generated
View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

View File

@@ -1,21 +0,0 @@
{
"arrowParens": "always",
"bracketSameLine": false,
"bracketSpacing": true,
"semi": true,
"experimentalTernaries": false,
"singleQuote": true,
"jsxSingleQuote": true,
"quoteProps": "as-needed",
"trailingComma": "all",
"singleAttributePerLine": true,
"htmlWhitespaceSensitivity": "css",
"vueIndentScriptAndStyle": false,
"proseWrap": "preserve",
"insertPragma": false,
"printWidth": 80,
"requirePragma": false,
"tabWidth": 2,
"useTabs": false,
"embeddedLanguageFormatting": "auto"
}

View File

@@ -1,16 +0,0 @@
{
"hooks": {
"before:init": [
"npm run lint"
]
},
"git": {
"commitMessage": "chore: release v${version}"
},
"github": {
"release": true
},
"npm": {
"publish": false
}
}

View File

@@ -1,3 +0,0 @@
{
"discord.enabled": false
}

View File

@@ -1,54 +0,0 @@
# Source : https://github.com/vercel/next.js/blob/canary/examples/with-docker/Dockerfile
FROM node:18-alpine AS base
# Install dependencies only when needed
FROM base AS deps
WORKDIR /app
# Install dependencies based on the preferred package manager
COPY package.json package-lock.json* ./
RUN npm pkg delete scripts.prepare
RUN npm ci --omit=dev
# Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
ENV NEXT_TELEMETRY_DISABLED 1
RUN npx prisma generate
RUN npm run build
# Production image, copy all the files and run next
FROM base AS runner
WORKDIR /app
ENV NODE_ENV production
ENV NEXT_TELEMETRY_DISABLED 1
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public
COPY --from=builder /app/prisma ./prisma
# Set the correct permission for prerender cache
RUN mkdir .next
RUN chown nextjs:nodejs .next
# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT 3000
# set hostname to localhost
ENV HOSTNAME "0.0.0.0"
CMD npx prisma migrate deploy && node server.js

View File

@@ -1,14 +0,0 @@
db:
docker compose -f dev.docker-compose.yml up -d --wait
dev: db
npx prisma db push
npx prisma generate
npm run dev
release:
npm run release
prod:
-docker network create mylinks_app
docker compose up -d --build --wait

View File

@@ -1,66 +0,0 @@
# MyLinks
MyLinks is a tool that lets you manage your favorite links in an intuitive interface.
Free and open source software, focused on privacy and self-hosting.
# Setup
Copy `example.env` file as `.env` and edit the properties.
```
cp example.env .env
```
## Dev
Leave the `DATABASE_URL` property filled
```
cd docker
make start-dev
cd ..
npx prisma db push
npm run dev
```
## Prod
If you want to use your own database leave, the `DATABASE_URL` property filled in `docker/docker-compose.yml` with your database credentials, otherwise you'll have to delete it.
```shell
cd docker
make build
make start-prod
```
## GitHub Actions
Env var to define :
```shell
DOCKER_USERNAME="Your docker username"
DOCKER_PASSWORD="Your docker password"
SSH_HOST="Your SSH host"
SSH_PORT="Your SSH port" # use port 22 if you are using the default value
SSH_USERNAME="Your SSH username" # private key
SSH_KEY="Your SSH key" # see below
```
> As a good practice, SSH Key should be generated on local machine instead of target/server/remote machine
Generate :
```shell
ssh-keygen -t rsa -b 4096
# you can save the file in your current folder since you're not supposed to use it personnaly (its purpose is only to be used by CI/CD)
```
Copy :
```shell
cat ./id_rsa.pub | ssh b@B 'cat >> ~/.ssh/authorized_keys'
# or
ssh-copy-id -i ./id_rsa.pub user@host
```
> Source: https://github.com/appleboy/ssh-action#setting-up-a-ssh-key

28
ace.js Normal file
View File

@@ -0,0 +1,28 @@
/*
|--------------------------------------------------------------------------
| JavaScript entrypoint for running ace commands
|--------------------------------------------------------------------------
|
| DO NOT MODIFY THIS FILE AS IT WILL BE OVERRIDDEN DURING THE BUILD
| PROCESS.
|
| See docs.adonisjs.com/guides/typescript-build-process#creating-production-build
|
| Since, we cannot run TypeScript source code using "node" binary, we need
| a JavaScript entrypoint to run ace commands.
|
| This file registers the "ts-node/esm" hook with the Node.js module system
| and then imports the "bin/console.ts" file.
|
*/
/**
* Register hook to process TypeScript files using ts-node
*/
import { register } from 'node:module';
register('ts-node/esm', import.meta.url);
/**
* Import ace console entrypoint
*/
await import('./bin/console.js');

102
adonisrc.ts Normal file
View File

@@ -0,0 +1,102 @@
import { defineConfig } from '@adonisjs/core/app';
export default defineConfig({
/*
|--------------------------------------------------------------------------
| Commands
|--------------------------------------------------------------------------
|
| List of ace commands to register from packages. The application commands
| will be scanned automatically from the "./commands" directory.
|
*/
commands: [() => import('@adonisjs/core/commands'), () => import('@adonisjs/lucid/commands')],
/*
|--------------------------------------------------------------------------
| Service providers
|--------------------------------------------------------------------------
|
| List of service providers to import and register when booting the
| application
|
*/
providers: [
() => import('@adonisjs/core/providers/app_provider'),
() => import('@adonisjs/core/providers/hash_provider'),
{
file: () => import('@adonisjs/core/providers/repl_provider'),
environment: ['repl', 'test'],
},
() => import('@adonisjs/core/providers/vinejs_provider'),
() => import('@adonisjs/core/providers/edge_provider'),
() => import('@adonisjs/session/session_provider'),
() => import('@adonisjs/vite/vite_provider'),
() => import('@adonisjs/shield/shield_provider'),
() => import('@adonisjs/static/static_provider'),
() => import('@adonisjs/cors/cors_provider'),
() => import('@adonisjs/lucid/database_provider'),
() => import('@adonisjs/auth/auth_provider'),
() => import('@adonisjs/inertia/inertia_provider'),
],
/*
|--------------------------------------------------------------------------
| Preloads
|--------------------------------------------------------------------------
|
| List of modules to import before starting the application.
|
*/
preloads: [() => import('#start/routes'), () => import('#start/kernel')],
/*
|--------------------------------------------------------------------------
| Tests
|--------------------------------------------------------------------------
|
| List of test suites to organize tests by their type. Feel free to remove
| and add additional suites.
|
*/
tests: {
suites: [
{
files: ['tests/unit/**/*.spec(.ts|.js)'],
name: 'unit',
timeout: 2000,
},
{
files: ['tests/functional/**/*.spec(.ts|.js)'],
name: 'functional',
timeout: 30000,
},
],
forceExit: false,
},
/*
|--------------------------------------------------------------------------
| Metafiles
|--------------------------------------------------------------------------
|
| A collection of files you want to copy to the build folder when creating
| the production build.
|
*/
metaFiles: [
{
pattern: 'resources/views/**/*.edge',
reloadServer: false,
},
{
pattern: 'public/**',
reloadServer: false,
},
],
assetsBundler: false,
unstable_assembler: {
onBuildStarting: [() => import('@adonisjs/vite/build_hook')],
},
});

45
app/exceptions/handler.ts Normal file
View File

@@ -0,0 +1,45 @@
import app from '@adonisjs/core/services/app';
import { HttpContext, ExceptionHandler } from '@adonisjs/core/http';
import type { StatusPageRange, StatusPageRenderer } from '@adonisjs/core/types/http';
export default class HttpExceptionHandler extends ExceptionHandler {
/**
* In debug mode, the exception handler will display verbose errors
* with pretty printed stack traces.
*/
protected debug = !app.inProduction;
/**
* Status pages are used to display a custom HTML pages for certain error
* codes. You might want to enable them in production only, but feel
* free to enable them in development as well.
*/
protected renderStatusPages = app.inProduction;
/**
* Status pages is a collection of error code range and a callback
* to return the HTML contents to send as a response.
*/
protected statusPages: Record<StatusPageRange, StatusPageRenderer> = {
'404': (error, { inertia }) => inertia.render('errors/not_found', { error }),
'500..599': (error, { inertia }) => inertia.render('errors/server_error', { error }),
};
/**
* The method is used for handling errors and returning
* response to the client
*/
async handle(error: unknown, ctx: HttpContext) {
return super.handle(error, ctx);
}
/**
* The method is used to report error to the logging service or
* the a third party error monitoring service.
*
* @note You should not attempt to send a response from this method.
*/
async report(error: unknown, ctx: HttpContext) {
return super.report(error, ctx);
}
}

View File

@@ -0,0 +1,25 @@
import type { HttpContext } from '@adonisjs/core/http';
import type { NextFn } from '@adonisjs/core/types/http';
import type { Authenticators } from '@adonisjs/auth/types';
/**
* Auth middleware is used authenticate HTTP requests and deny
* access to unauthenticated users.
*/
export default class AuthMiddleware {
/**
* The URL to redirect to, when authentication fails
*/
redirectTo = '/login';
async handle(
ctx: HttpContext,
next: NextFn,
options: {
guards?: (keyof Authenticators)[];
} = {}
) {
await ctx.auth.authenticateUsing(options.guards, { loginRoute: this.redirectTo });
return next();
}
}

View File

@@ -0,0 +1,19 @@
import { Logger } from '@adonisjs/core/logger';
import { HttpContext } from '@adonisjs/core/http';
import { NextFn } from '@adonisjs/core/types/http';
/**
* The container bindings middleware binds classes to their request
* specific value using the container resolver.
*
* - We bind "HttpContext" class to the "ctx" object
* - And bind "Logger" class to the "ctx.logger" object
*/
export default class ContainerBindingsMiddleware {
handle(ctx: HttpContext, next: NextFn) {
ctx.containerResolver.bindValue(HttpContext, ctx);
ctx.containerResolver.bindValue(Logger, ctx.logger);
return next();
}
}

View File

@@ -0,0 +1,31 @@
import type { HttpContext } from '@adonisjs/core/http';
import type { NextFn } from '@adonisjs/core/types/http';
import type { Authenticators } from '@adonisjs/auth/types';
/**
* Guest middleware is used to deny access to routes that should
* be accessed by unauthenticated users.
*
* For example, the login page should not be accessible if the user
* is already logged-in
*/
export default class GuestMiddleware {
/**
* The URL to redirect to when user is logged-in
*/
redirectTo = '/';
async handle(
ctx: HttpContext,
next: NextFn,
options: { guards?: (keyof Authenticators)[] } = {}
) {
for (let guard of options.guards || [ctx.auth.defaultGuard]) {
if (await ctx.auth.use(guard).check()) {
return ctx.response.redirect(this.redirectTo, true);
}
}
return next();
}
}

30
app/models/user.ts Normal file
View File

@@ -0,0 +1,30 @@
import { DateTime } from 'luxon';
import hash from '@adonisjs/core/services/hash';
import { compose } from '@adonisjs/core/helpers';
import { BaseModel, column } from '@adonisjs/lucid/orm';
import { withAuthFinder } from '@adonisjs/auth/mixins/lucid';
const AuthFinder = withAuthFinder(() => hash.use('scrypt'), {
uids: ['email'],
passwordColumnName: 'password',
});
export default class User extends compose(BaseModel, AuthFinder) {
@column({ isPrimary: true })
declare id: number;
@column()
declare fullName: string | null;
@column()
declare email: string;
@column()
declare password: string;
@column.dateTime({ autoCreate: true })
declare createdAt: DateTime;
@column.dateTime({ autoCreate: true, autoUpdate: true })
declare updatedAt: DateTime | null;
}

47
bin/console.ts Normal file
View File

@@ -0,0 +1,47 @@
/*
|--------------------------------------------------------------------------
| Ace entry point
|--------------------------------------------------------------------------
|
| The "console.ts" file is the entrypoint for booting the AdonisJS
| command-line framework and executing commands.
|
| Commands do not boot the application, unless the currently running command
| has "options.startApp" flag set to true.
|
*/
import 'reflect-metadata';
import { Ignitor, prettyPrintError } from '@adonisjs/core';
/**
* URL to the application root. AdonisJS need it to resolve
* paths to file and directories for scaffolding commands
*/
const APP_ROOT = new URL('../', import.meta.url);
/**
* The importer is used to import files in context of the
* application.
*/
const IMPORTER = (filePath: string) => {
if (filePath.startsWith('./') || filePath.startsWith('../')) {
return import(new URL(filePath, APP_ROOT).href);
}
return import(filePath);
};
new Ignitor(APP_ROOT, { importer: IMPORTER })
.tap((app) => {
app.booting(async () => {
await import('#start/env');
});
app.listen('SIGTERM', () => app.terminate());
app.listenIf(app.managedByPm2, 'SIGINT', () => app.terminate());
})
.ace()
.handle(process.argv.splice(2))
.catch((error) => {
process.exitCode = 1;
prettyPrintError(error);
});

45
bin/server.ts Normal file
View File

@@ -0,0 +1,45 @@
/*
|--------------------------------------------------------------------------
| HTTP server entrypoint
|--------------------------------------------------------------------------
|
| The "server.ts" file is the entrypoint for starting the AdonisJS HTTP
| server. Either you can run this file directly or use the "serve"
| command to run this file and monitor file changes
|
*/
import 'reflect-metadata';
import { Ignitor, prettyPrintError } from '@adonisjs/core';
/**
* URL to the application root. AdonisJS need it to resolve
* paths to file and directories for scaffolding commands
*/
const APP_ROOT = new URL('../', import.meta.url);
/**
* The importer is used to import files in context of the
* application.
*/
const IMPORTER = (filePath: string) => {
if (filePath.startsWith('./') || filePath.startsWith('../')) {
return import(new URL(filePath, APP_ROOT).href);
}
return import(filePath);
};
new Ignitor(APP_ROOT, { importer: IMPORTER })
.tap((app) => {
app.booting(async () => {
await import('#start/env');
});
app.listen('SIGTERM', () => app.terminate());
app.listenIf(app.managedByPm2, 'SIGINT', () => app.terminate());
})
.httpServer()
.start()
.catch((error) => {
process.exitCode = 1;
prettyPrintError(error);
});

62
bin/test.ts Normal file
View File

@@ -0,0 +1,62 @@
/*
|--------------------------------------------------------------------------
| Test runner entrypoint
|--------------------------------------------------------------------------
|
| The "test.ts" file is the entrypoint for running tests using Japa.
|
| Either you can run this file directly or use the "test"
| command to run this file and monitor file changes.
|
*/
process.env.NODE_ENV = 'test';
import 'reflect-metadata';
import { Ignitor, prettyPrintError } from '@adonisjs/core';
import { configure, processCLIArgs, run } from '@japa/runner';
/**
* URL to the application root. AdonisJS need it to resolve
* paths to file and directories for scaffolding commands
*/
const APP_ROOT = new URL('../', import.meta.url);
/**
* The importer is used to import files in context of the
* application.
*/
const IMPORTER = (filePath: string) => {
if (filePath.startsWith('./') || filePath.startsWith('../')) {
return import(new URL(filePath, APP_ROOT).href);
}
return import(filePath);
};
new Ignitor(APP_ROOT, { importer: IMPORTER })
.tap((app) => {
app.booting(async () => {
await import('#start/env');
});
app.listen('SIGTERM', () => app.terminate());
app.listenIf(app.managedByPm2, 'SIGINT', () => app.terminate());
})
.testRunner()
.configure(async (app) => {
const { runnerHooks, ...config } = await import('../tests/bootstrap.js');
processCLIArgs(process.argv.splice(2));
configure({
...app.rcFile.tests,
...config,
...{
setup: runnerHooks.setup,
teardown: runnerHooks.teardown.concat([() => app.terminate()]),
},
});
})
.run(() => run())
.catch((error) => {
process.exitCode = 1;
prettyPrintError(error);
});

View File

@@ -1,8 +0,0 @@
const config = {
url: process.env.NEXT_PUBLIC_SITE_URL,
name: 'MyLinks',
description:
'MyLinks is a free and open source software, that lets you manage your bookmarks in an intuitive interface',
};
export default config;

40
config/app.ts Normal file
View File

@@ -0,0 +1,40 @@
import env from '#start/env';
import app from '@adonisjs/core/services/app';
import { Secret } from '@adonisjs/core/helpers';
import { defineConfig } from '@adonisjs/core/http';
/**
* The app key is used for encrypting cookies, generating signed URLs,
* and by the "encryption" module.
*
* The encryption module will fail to decrypt data if the key is lost or
* changed. Therefore it is recommended to keep the app key secure.
*/
export const appKey = new Secret(env.get('APP_KEY'));
/**
* The configuration settings used by the HTTP server
*/
export const http = defineConfig({
generateRequestId: true,
allowMethodSpoofing: false,
/**
* Enabling async local storage will let you access HTTP context
* from anywhere inside your application.
*/
useAsyncLocalStorage: false,
/**
* Manage cookies configuration. The settings for the session id cookie are
* defined inside the "config/session.ts" file.
*/
cookie: {
domain: '',
path: '/',
maxAge: '2h',
httpOnly: true,
secure: app.inProduction,
sameSite: 'lax',
},
});

28
config/auth.ts Normal file
View File

@@ -0,0 +1,28 @@
import { defineConfig } from '@adonisjs/auth';
import { InferAuthEvents, Authenticators } from '@adonisjs/auth/types';
import { sessionGuard, sessionUserProvider } from '@adonisjs/auth/session';
const authConfig = defineConfig({
default: 'web',
guards: {
web: sessionGuard({
useRememberMeTokens: false,
provider: sessionUserProvider({
model: () => import('#models/user'),
}),
}),
},
});
export default authConfig;
/**
* Inferring types from the configured auth
* guards.
*/
declare module '@adonisjs/auth/types' {
interface Authenticators extends InferAuthenticators<typeof authConfig> {}
}
declare module '@adonisjs/core/types' {
interface EventsList extends InferAuthEvents<Authenticators> {}
}

55
config/bodyparser.ts Normal file
View File

@@ -0,0 +1,55 @@
import { defineConfig } from '@adonisjs/core/bodyparser';
const bodyParserConfig = defineConfig({
/**
* The bodyparser middleware will parse the request body
* for the following HTTP methods.
*/
allowedMethods: ['POST', 'PUT', 'PATCH', 'DELETE'],
/**
* Config for the "application/x-www-form-urlencoded"
* content-type parser
*/
form: {
convertEmptyStringsToNull: true,
types: ['application/x-www-form-urlencoded'],
},
/**
* Config for the JSON parser
*/
json: {
convertEmptyStringsToNull: true,
types: [
'application/json',
'application/json-patch+json',
'application/vnd.api+json',
'application/csp-report',
],
},
/**
* Config for the "multipart/form-data" content-type parser.
* File uploads are handled by the multipart parser.
*/
multipart: {
/**
* Enabling auto process allows bodyparser middleware to
* move all uploaded files inside the tmp folder of your
* operating system
*/
autoProcess: true,
convertEmptyStringsToNull: true,
processManually: [],
/**
* Maximum limit of data to parse including all files
* and fields
*/
limit: '20mb',
types: ['multipart/form-data'],
},
});
export default bodyParserConfig;

19
config/cors.ts Normal file
View File

@@ -0,0 +1,19 @@
import { defineConfig } from '@adonisjs/cors';
/**
* Configuration options to tweak the CORS policy. The following
* options are documented on the official documentation website.
*
* https://docs.adonisjs.com/guides/security/cors
*/
const corsConfig = defineConfig({
enabled: true,
origin: [],
methods: ['GET', 'HEAD', 'POST', 'PUT', 'DELETE'],
headers: true,
exposeHeaders: [],
credentials: true,
maxAge: 90,
});
export default corsConfig;

24
config/database.ts Normal file
View File

@@ -0,0 +1,24 @@
import env from '#start/env';
import { defineConfig } from '@adonisjs/lucid';
const dbConfig = defineConfig({
connection: 'postgres',
connections: {
postgres: {
client: 'pg',
connection: {
host: env.get('DB_HOST'),
port: env.get('DB_PORT'),
user: env.get('DB_USER'),
password: env.get('DB_PASSWORD'),
database: env.get('DB_DATABASE'),
},
migrations: {
naturalSort: true,
paths: ['database/migrations'],
},
},
},
});
export default dbConfig;

24
config/hash.ts Normal file
View File

@@ -0,0 +1,24 @@
import { defineConfig, drivers } from '@adonisjs/core/hash';
const hashConfig = defineConfig({
default: 'scrypt',
list: {
scrypt: drivers.scrypt({
cost: 16384,
blockSize: 8,
parallelization: 1,
maxMemory: 33554432,
}),
},
});
export default hashConfig;
/**
* Inferring types for the list of hashers you have configured
* in your application.
*/
declare module '@adonisjs/core/types' {
export interface HashersList extends InferHashers<typeof hashConfig> {}
}

23
config/inertia.ts Normal file
View File

@@ -0,0 +1,23 @@
import { defineConfig } from '@adonisjs/inertia';
export default defineConfig({
/**
* Path to the Edge view that will be used as the root view for Inertia responses
*/
rootView: 'inertia_layout',
/**
* Data that should be shared with all rendered pages
*/
sharedData: {
errors: (ctx) => ctx.session?.flashMessages.get('errors'),
},
/**
* Options for the server-side rendering
*/
ssr: {
enabled: true,
entrypoint: 'inertia/app/ssr.tsx',
},
});

35
config/logger.ts Normal file
View File

@@ -0,0 +1,35 @@
import env from '#start/env';
import app from '@adonisjs/core/services/app';
import { defineConfig, targets } from '@adonisjs/core/logger';
const loggerConfig = defineConfig({
default: 'app',
/**
* The loggers object can be used to define multiple loggers.
* By default, we configure only one logger (named "app").
*/
loggers: {
app: {
enabled: true,
name: env.get('APP_NAME'),
level: env.get('LOG_LEVEL'),
transport: {
targets: targets()
.pushIf(!app.inProduction, targets.pretty())
.pushIf(app.inProduction, targets.file({ destination: 1 }))
.toArray(),
},
},
},
});
export default loggerConfig;
/**
* Inferring types for the list of loggers you have configured
* in your application.
*/
declare module '@adonisjs/core/types' {
export interface LoggersList extends InferLoggers<typeof loggerConfig> {}
}

48
config/session.ts Normal file
View File

@@ -0,0 +1,48 @@
import env from '#start/env';
import app from '@adonisjs/core/services/app';
import { defineConfig, stores } from '@adonisjs/session';
const sessionConfig = defineConfig({
enabled: true,
cookieName: 'adonis-session',
/**
* When set to true, the session id cookie will be deleted
* once the user closes the browser.
*/
clearWithBrowser: false,
/**
* Define how long to keep the session data alive without
* any activity.
*/
age: '2h',
/**
* Configuration for session cookie and the
* cookie store
*/
cookie: {
path: '/',
httpOnly: true,
secure: app.inProduction,
sameSite: 'lax',
},
/**
* The store to use. Make sure to validate the environment
* variable in order to infer the store name without any
* errors.
*/
store: env.get('SESSION_DRIVER'),
/**
* List of configured stores. Refer documentation to see
* list of available stores and their config.
*/
stores: {
cookie: stores.cookie(),
},
});
export default sessionConfig;

51
config/shield.ts Normal file
View File

@@ -0,0 +1,51 @@
import { defineConfig } from '@adonisjs/shield';
const shieldConfig = defineConfig({
/**
* Configure CSP policies for your app. Refer documentation
* to learn more
*/
csp: {
enabled: false,
directives: {},
reportOnly: false,
},
/**
* Configure CSRF protection options. Refer documentation
* to learn more
*/
csrf: {
enabled: true,
exceptRoutes: [],
enableXsrfCookie: true,
methods: ['POST', 'PUT', 'PATCH', 'DELETE'],
},
/**
* Control how your website should be embedded inside
* iFrames
*/
xFrame: {
enabled: true,
action: 'DENY',
},
/**
* Force browser to always use HTTPS
*/
hsts: {
enabled: true,
maxAge: '180 days',
},
/**
* Disable browsers from sniffing the content type of a
* response and always rely on the "content-type" header.
*/
contentTypeSniffing: {
enabled: true,
},
});
export default shieldConfig;

17
config/static.ts Normal file
View File

@@ -0,0 +1,17 @@
import { defineConfig } from '@adonisjs/static';
/**
* Configuration options to tweak the static files middleware.
* The complete set of options are documented on the
* official documentation website.
*
* https://docs.adonisjs.com/guides/static-assets
*/
const staticServerConfig = defineConfig({
enabled: true,
etag: true,
lastModified: true,
dotFiles: 'ignore',
});
export default staticServerConfig;

28
config/vite.ts Normal file
View File

@@ -0,0 +1,28 @@
import { defineConfig } from '@adonisjs/vite';
const viteBackendConfig = defineConfig({
/**
* The output of vite will be written inside this
* directory. The path should be relative from
* the application root.
*/
buildDirectory: 'public/assets',
/**
* The path to the manifest file generated by the
* "vite build" command.
*/
manifestFile: 'public/assets/.vite/manifest.json',
/**
* Feel free to change the value of the "assetsUrl" to
* point to a CDN in production.
*/
assetsUrl: '/assets',
scriptAttributes: {
defer: true,
},
});
export default viteBackendConfig;

View File

@@ -0,0 +1,21 @@
import { BaseSchema } from '@adonisjs/lucid/schema';
export default class extends BaseSchema {
protected tableName = 'users';
async up() {
this.schema.createTable(this.tableName, (table) => {
table.increments('id').notNullable();
table.string('full_name').nullable();
table.string('email', 254).notNullable().unique();
table.string('password').notNullable();
table.timestamp('created_at').notNullable();
table.timestamp('updated_at').nullable();
});
}
async down() {
this.schema.dropTable(this.tableName);
}
}

View File

@@ -1,27 +0,0 @@
services:
mysqldb:
image: mysql:latest
restart: always
env_file:
- .env
ports:
- '3306:3306'
healthcheck:
test: mysqladmin ping -h 127.0.0.1 -u $$MYSQL_USER --password=$$MYSQL_PASSWORD
start_period: 5s
interval: 5s
timeout: 5s
retries: 55
phpmyadmin:
image: phpmyadmin:5
restart: always
environment:
- PMA_HOST=mysqldb
- PMA_PORT=3306
env_file:
- .env
ports:
- '8080:80'
depends_on:
- mysqldb

View File

@@ -1,42 +0,0 @@
networks:
mylinks_app:
external: true
services:
mylinks:
restart: always
container_name: MyLinks
build:
context: .
ports:
- '127.0.0.1:3000:3000'
env_file:
- .env
environment:
- DATABASE_URL=mysql://${MYSQL_USER}:${MYSQL_PASSWORD}@mylinks_db:3306/${MYSQL_DATABASE}
networks:
- mylinks_app
healthcheck:
test:
[
'CMD-SHELL',
'wget --spider --tries=1 --no-verbose http://0.0.0.0:3000',
]
depends_on:
mylinks_db:
condition: service_started
mylinks_db:
container_name: MyLinksDB
image: mysql:latest
restart: always
env_file:
- .env
healthcheck:
test: mysqladmin ping -h 127.0.0.1 -u $$MYSQL_USER --password=$$MYSQL_PASSWORD
start_period: 5s
interval: 5s
timeout: 5s
retries: 55
networks:
- mylinks_app

View File

@@ -1,17 +0,0 @@
MYSQL_USER="mluser"
MYSQL_PASSWORD="root"
MYSQL_ROOT_PASSWORD="root"
MYSQL_DATABASE="mylinks"
DATABASE_URL="mysql://root:${MYSQL_ROOT_PASSWORD}@localhost:3306/${MYSQL_DATABASE}"
NEXTAUTH_URL="http://localhost:3000"
NEXT_PUBLIC_SITE_URL="http://localhost:3000"
NEXTAUTH_SECRET=""
GOOGLE_CLIENT_ID=""
GOOGLE_CLIENT_SECRET=""
NEXT_PUBLIC_UMAMI_SCRIPT_ORIGIN="http://localhost:4000"
NEXT_PUBLIC_UMAMI_WEBSITE_ID=""

20
inertia/app/app.tsx Normal file
View File

@@ -0,0 +1,20 @@
import '../css/app.css';
import { hydrateRoot } from 'react-dom/client';
import { createInertiaApp } from '@inertiajs/react';
import { resolvePageComponent } from '@adonisjs/inertia/helpers';
const appName = import.meta.env.VITE_APP_NAME || 'AdonisJS';
createInertiaApp({
progress: { color: '#5468FF' },
title: (title) => `${title} - ${appName}`,
resolve: (name) => {
return resolvePageComponent(`../pages/${name}.tsx`, import.meta.glob('../pages/**/*.tsx'));
},
setup({ el, App, props }) {
hydrateRoot(el, <App {...props} />);
},
});

14
inertia/app/ssr.tsx Normal file
View File

@@ -0,0 +1,14 @@
import ReactDOMServer from 'react-dom/server';
import { createInertiaApp } from '@inertiajs/react';
export default function render(page: any) {
return createInertiaApp({
page,
render: ReactDOMServer.renderToString,
resolve: (name) => {
const pages = import.meta.glob('../pages/**/*.tsx', { eager: true });
return pages[`../pages/${name}.tsx`];
},
setup: ({ App, props }) => <App {...props} />,
});
}

36
inertia/css/app.css Normal file
View File

@@ -0,0 +1,36 @@
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;500&display=swap');
* {
margin: 0;
padding: 0;
}
html,
body,
#app {
background-color: #f7f8fa;
font-family: 'Poppins', sans-serif;
color: #46444c;
height: 100%;
width: 100%;
}
.title {
font-size: 42px;
font-weight: 500;
color: #5a45ff;
}
.container {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
height: 100%;
width: 100%;
}
a {
text-decoration: underline;
color: #5a45ff;
}

View File

@@ -0,0 +1,11 @@
export default function NotFound() {
return (
<>
<div className="container">
<div className="title">Page not found</div>
<span>This page does not exist.</span>
</div>
</>
);
}

View File

@@ -0,0 +1,11 @@
export default function ServerError(props: { error: any }) {
return (
<>
<div className="container">
<div className="title">Server Error</div>
<span>{props.error.message}</span>
</div>
</>
);
}

18
inertia/pages/home.tsx Normal file
View File

@@ -0,0 +1,18 @@
import { Head } from '@inertiajs/react';
export default function Home(props: { version: number }) {
return (
<>
<Head title="Homepage" />
<div className="container">
<div className="title">AdonisJS {props.version} x Inertia x React</div>
<span>
Learn more about AdonisJS and Inertia.js by visiting the{' '}
<a href="https://docs.adonisjs.com/guides/inertia">AdonisJS documentation</a>.
</span>
</div>
</>
);
}

12
inertia/tsconfig.json Normal file
View File

@@ -0,0 +1,12 @@
{
"extends": "@adonisjs/tsconfig/tsconfig.client.json",
"compilerOptions": {
"baseUrl": ".",
"module": "ESNext",
"jsx": "react-jsx",
"paths": {
"~/*": ["./*"]
}
},
"include": ["./**/*.ts", "./**/*.tsx"]
}

View File

@@ -1,47 +0,0 @@
import acceptLanguage from 'accept-language';
import { NextResponse } from 'next/server';
import { i18n } from './next-i18next.config';
acceptLanguage.languages(i18n.locales);
export const config = {
matcher: ['/((?!api|_next/static|_next/image|assets|favicon.ico|sw.js).*)'],
};
const cookieName = 'i18next';
// Source : https://github.com/i18next/next-app-dir-i18next-example/blob/3d653a46ae33f46abc011b6186f7a4595b84129f/middleware.js
export function middleware(req) {
if (
req.nextUrl.pathname.indexOf('icon') > -1 ||
req.nextUrl.pathname.indexOf('chrome') > -1
)
return NextResponse.next();
let lng;
if (req.cookies.has(cookieName))
lng = acceptLanguage.get(req.cookies.get(cookieName).value);
if (!lng) lng = acceptLanguage.get(req.headers.get('Accept-Language'));
if (!lng) lng = i18n.defaultLocale;
// Redirect if lng in path is not supported
if (
!i18n.locales.some((loc) => req.nextUrl.pathname.startsWith(`/${loc}`)) &&
!req.nextUrl.pathname.startsWith('/_next')
) {
return NextResponse.redirect(
new URL(`/${lng}${req.nextUrl.pathname}`, req.url),
);
}
if (req.headers.has('referer')) {
const refererUrl = new URL(req.headers.get('referer'));
const lngInReferer = i18n.locales.find((l) =>
refererUrl.pathname.startsWith(`/${l}`),
);
const response = NextResponse.next();
if (lngInReferer) response.cookies.set(cookieName, lngInReferer);
return response;
}
return NextResponse.next();
}

5
next-env.d.ts vendored
View File

@@ -1,5 +0,0 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.

View File

@@ -1,10 +0,0 @@
/** @type {import('next-i18next').UserConfig} */
module.exports = {
// debug: process.env.NODE_ENV === "development",
i18n: {
defaultLocale: 'en',
locales: ['en', 'fr'],
},
reloadOnPrerender: process.env.NODE_ENV === 'development',
returnNull: false,
};

View File

@@ -1,7 +0,0 @@
/** @type {import('next-sitemap').IConfig} */
module.exports = {
siteUrl: process.env.NEXTAUTH_URL || 'https://www.mylinks.app',
generateRobotsTxt: true,
output: 'standalone',
exclude: ['/link/*', '/category/*'],
};

View File

@@ -1,39 +0,0 @@
const { i18n } = require('./next-i18next.config');
const {
PHASE_DEVELOPMENT_SERVER,
PHASE_PRODUCTION_BUILD,
} = require('next/constants');
/** @type {import("next").NextConfig} */
const nextConfig = {
i18n,
webpack(config) {
config.module.rules.push({
test: /\.svg$/,
use: ['@svgr/webpack'],
});
return config;
},
images: {
remotePatterns: [
{ hostname: 'localhost' },
{ hostname: 't3.gstatic.com' },
{ hostname: 'lh3.googleusercontent.com' },
{ hostname: 'www.mylinks.app' },
],
formats: ['image/webp'],
},
reactStrictMode: false,
output: 'standalone',
};
module.exports = (phase) => {
if (phase === PHASE_DEVELOPMENT_SERVER || phase === PHASE_PRODUCTION_BUILD) {
const withPWA = require('@ducanh2912/next-pwa').default({
dest: 'public',
});
return withPWA(nextConfig);
}
return nextConfig;
};

15422
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,64 +1,93 @@
{
"name": "my-links",
"version": "1.3.0",
"description": "MyLinks is a free and open source software, that lets you manage your bookmarks in an intuitive interface",
"private": false,
"version": "0.0.0",
"private": true,
"type": "module",
"license": "UNLICENSED",
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"postbuild": "next-sitemap",
"prepare": "husky install",
"release": "release-it"
"start": "node bin/server.js",
"build": "node ace build",
"dev": "node ace serve --watch",
"test": "node ace test",
"lint": "eslint .",
"format": "prettier --write .",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@ducanh2912/next-pwa": "^10.2.6",
"@prisma/client": "^5.12.1",
"@svgr/webpack": "^8.1.0",
"@types/react-toggle": "^4.0.5",
"accept-language": "^3.0.18",
"clsx": "^2.1.0",
"dayjs": "^1.11.10",
"framer-motion": "^11.0.28",
"i18next": "^23.11.2",
"next": "^14.2.1",
"next-auth": "^4.24.7",
"next-i18next": "^15.2.0",
"next-seo": "^6.5.0",
"next-sitemap": "^4.2.3",
"node-html-parser": "^6.1.13",
"nprogress": "^0.2.0",
"react": "^18.2.0",
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",
"react-dom": "^18.2.0",
"react-hotkeys-hook": "^4.5.0",
"react-i18next": "^14.1.0",
"react-icons": "^5.0.1",
"react-select": "^5.8.0",
"react-swipeable": "^7.0.1",
"react-tabs": "^6.0.2",
"react-toggle": "^4.1.3",
"sass": "^1.75.0",
"sharp": "^0.33.3",
"yup": "^1.4.0"
"imports": {
"#controllers/*": "./app/controllers/*.js",
"#exceptions/*": "./app/exceptions/*.js",
"#models/*": "./app/models/*.js",
"#mails/*": "./app/mails/*.js",
"#services/*": "./app/services/*.js",
"#listeners/*": "./app/listeners/*.js",
"#events/*": "./app/events/*.js",
"#middleware/*": "./app/middleware/*.js",
"#validators/*": "./app/validators/*.js",
"#providers/*": "./providers/*.js",
"#policies/*": "./app/policies/*.js",
"#abilities/*": "./app/abilities/*.js",
"#database/*": "./database/*.js",
"#tests/*": "./tests/*.js",
"#start/*": "./start/*.js",
"#config/*": "./config/*.js"
},
"devDependencies": {
"@adonisjs/assembler": "^7.5.1",
"@adonisjs/eslint-config": "^1.3.0",
"@adonisjs/tsconfig": "^1.3.0",
"@japa/assert": "^3.0.0",
"@japa/plugin-adonisjs": "^3.0.1",
"@japa/runner": "^3.1.4",
"@swc/core": "^1.4.16",
"@types/luxon": "^3.4.2",
"@types/node": "^20.12.7",
"@types/nprogress": "^0.2.3",
"@types/react": "^18.2.78",
"@typescript-eslint/eslint-plugin": "^6.14.0",
"@typescript-eslint/parser": "^6.14.0",
"eslint": "^8.56.0",
"eslint-config-next": "14.2.1",
"husky": "^9.0.11",
"lint-staged": "^15.2.2",
"prisma": "^5.12.1",
"release-it": "^17.2.0",
"typescript": "5.4.5"
"@types/react": "^18.3.1",
"@types/react-dom": "^18.3.0",
"@vitejs/plugin-react": "^4.2.1",
"eslint": "^8.57.0",
"hot-hook": "^0.2.1",
"pino-pretty": "^11.0.0",
"prettier": "^3.2.5",
"ts-node": "^10.9.2",
"typescript": "^5.4.5",
"vite": "^5.2.10"
},
"lint-staged": {
"*.js": "eslint --cache --fix"
"dependencies": {
"@adonisjs/auth": "^9.2.0",
"@adonisjs/core": "^6.8.0",
"@adonisjs/cors": "^2.2.1",
"@adonisjs/inertia": "1.0.0-25",
"@adonisjs/lucid": "^20.5.1",
"@adonisjs/session": "^7.4.0",
"@adonisjs/shield": "^8.1.1",
"@adonisjs/static": "^1.1.1",
"@adonisjs/vite": "^3.0.0-11",
"@inertiajs/react": "^1.0.16",
"@vinejs/vine": "^2.0.0",
"edge.js": "^6.0.2",
"luxon": "^3.4.4",
"pg": "^8.11.5",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"reflect-metadata": "^0.2.2"
},
"hotHook": {
"boundaries": [
"./app/controllers/**/*.ts",
"./app/middleware/*.ts"
]
},
"eslintConfig": {
"extends": "@adonisjs/eslint-config/app"
},
"prettier": {
"trailingComma": "es5",
"semi": true,
"singleQuote": true,
"useTabs": false,
"quoteProps": "consistent",
"bracketSpacing": true,
"arrowParens": "always",
"printWidth": 100
}
}

View File

@@ -1,41 +0,0 @@
-- CreateTable
CREATE TABLE `user` (
`id` INTEGER NOT NULL AUTO_INCREMENT,
`google_id` VARCHAR(191) NOT NULL,
`email` VARCHAR(191) NOT NULL,
`createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`updatedAt` DATETIME(3) NOT NULL,
UNIQUE INDEX `user_google_id_key`(`google_id`),
UNIQUE INDEX `user_email_key`(`email`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `category` (
`id` INTEGER NOT NULL AUTO_INCREMENT,
`name` VARCHAR(255) NOT NULL,
`nextCategoryId` INTEGER NOT NULL DEFAULT 0,
`createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`updatedAt` DATETIME(3) NOT NULL,
UNIQUE INDEX `category_name_key`(`name`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `link` (
`id` INTEGER NOT NULL AUTO_INCREMENT,
`name` VARCHAR(255) NOT NULL,
`url` TEXT NOT NULL,
`categoryId` INTEGER NOT NULL,
`nextLinkId` INTEGER NOT NULL DEFAULT 0,
`favorite` BOOLEAN NOT NULL DEFAULT false,
`createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`updatedAt` DATETIME(3) NOT NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- AddForeignKey
ALTER TABLE `link` ADD CONSTRAINT `link_categoryId_fkey` FOREIGN KEY (`categoryId`) REFERENCES `category`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@@ -1,17 +0,0 @@
/*
Warnings:
- You are about to drop the column `nextCategoryId` on the `category` table. All the data in the column will be lost.
- You are about to drop the column `nextLinkId` on the `link` table. All the data in the column will be lost.
- Added the required column `authorId` to the `category` table without a default value. This is not possible if the table is not empty.
*/
-- AlterTable
ALTER TABLE `category` DROP COLUMN `nextCategoryId`,
ADD COLUMN `authorId` INTEGER NOT NULL;
-- AlterTable
ALTER TABLE `link` DROP COLUMN `nextLinkId`;
-- AddForeignKey
ALTER TABLE `category` ADD CONSTRAINT `category_authorId_fkey` FOREIGN KEY (`authorId`) REFERENCES `user`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@@ -1,11 +0,0 @@
/*
Warnings:
- Added the required column `authorId` to the `link` table without a default value. This is not possible if the table is not empty.
*/
-- AlterTable
ALTER TABLE `link` ADD COLUMN `authorId` INTEGER NOT NULL;
-- AddForeignKey
ALTER TABLE `link` ADD CONSTRAINT `link_authorId_fkey` FOREIGN KEY (`authorId`) REFERENCES `user`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@@ -1,2 +0,0 @@
-- DropIndex
DROP INDEX `category_name_key` ON `category`;

View File

@@ -1,3 +0,0 @@
-- AlterTable
ALTER TABLE `category` ADD COLUMN `nextId` INTEGER NULL;

View File

@@ -1,3 +0,0 @@
-- AlterTable
ALTER TABLE `user` ADD COLUMN `name` VARCHAR(191) NULL AFTER `email`,
ADD COLUMN `image` VARCHAR(191) NULL AFTER `name`;

View File

@@ -1,2 +0,0 @@
-- AlterTable
ALTER TABLE `user` ADD COLUMN `is_admin` BOOLEAN NOT NULL DEFAULT false;

View File

@@ -1,2 +0,0 @@
-- AlterTable
ALTER TABLE `link` ADD COLUMN `description` VARCHAR(255) NULL;

View File

@@ -1,2 +0,0 @@
-- AlterTable
ALTER TABLE `category` ADD COLUMN `description` VARCHAR(255) NULL;

View File

@@ -1,2 +0,0 @@
-- AlterTable
ALTER TABLE `category` ADD COLUMN `visibility` ENUM('private', 'public') NOT NULL DEFAULT 'private';

View File

@@ -1,3 +0,0 @@
# Please do not edit this file manually
# It should be added in your version-control system (i.e. Git)
provider = "mysql"

View File

@@ -1,10 +0,0 @@
-- set default next id for each category based on LEAD(id) (LEAD -> Next)
UPDATE category AS c
JOIN (
SELECT
id,
LEAD(id) OVER (PARTITION BY authorId ORDER BY id) AS nextCategoryId
FROM category
) AS n ON c.id = n.id
SET c.nextId = n.nextCategoryId;

View File

@@ -1,71 +0,0 @@
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
shadowDatabaseUrl = env("SHADOW_DATABASE_URL")
}
model User {
id Int @id @default(autoincrement())
google_id String @unique
email String @unique
name String?
image String?
is_admin Boolean @default(false)
categories Category[]
links Link[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("user")
}
model Category {
id Int @id @default(autoincrement())
name String @db.VarChar(255)
description String? @db.VarChar(255)
links Link[]
visibility Visibility @default(private)
author User @relation(fields: [authorId], references: [id])
authorId Int
nextId Int?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("category")
}
model Link {
id Int @id @default(autoincrement())
name String @db.VarChar(255)
description String? @db.VarChar(255)
url String @db.Text
category Category @relation(fields: [categoryId], references: [id])
categoryId Int
author User @relation(fields: [authorId], references: [id])
authorId Int
favorite Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("link")
}
enum Visibility {
private
public
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 596 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<svg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
<path d="M 106.813 73.354 L 86.501 73.355 C 86.501 84.421 95.594 93.398 106.813 93.398 C 124.952 93.398 139.996 108.476 139.996 126.656 L 140 126.656 C 140 144.835 124.956 159.913 106.816 159.913 L 0 159.913 L 62.001 200 L 62.001 189.977 C 62.001 184.441 66.476 179.956 72 179.956 L 106.816 179.956 L 106.816 179.962 C 136.14 179.962 159.999 156.048 159.999 126.656 C 159.999 97.266 136.14 73.354 106.813 73.354 Z" fill="#3f88c5" />
<path d="M 93.184 126.646 L 113.498 126.645 C 113.498 115.579 104.402 106.603 93.184 106.603 C 75.042 106.603 60 91.524 60 73.344 C 60 55.165 75.044 40.09 93.184 40.09 L 200 40.09 L 137.999 0 L 137.999 10.023 C 137.999 15.559 133.524 20.044 128 20.044 L 93.184 20.044 L 93.184 20.039 C 63.86 20.039 40.001 43.951 40.001 73.344 L 39.997 73.344 C 39.997 102.735 63.855 126.646 93.184 126.646 Z" fill="#3f88c5" />
</svg>

Before

Width:  |  Height:  |  Size: 957 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 30"><clipPath id="a"><path d="M0 0v30h60V0z"/></clipPath><clipPath id="b"><path d="M30 15h30v15zv15H0zH0V0zV0h30z"/></clipPath><g clip-path="url(#a)"><path d="M0 0v30h60V0z" fill="#012169"/><path d="M0 0l60 30m0-30L0 30" stroke="#fff" stroke-width="6"/><path d="M0 0l60 30m0-30L0 30" clip-path="url(#b)" stroke="#C8102E" stroke-width="4"/><path d="M30 0v30M0 15h60" stroke="#fff" stroke-width="10"/><path d="M30 0v30M0 15h60" stroke="#C8102E" stroke-width="6"/></g></svg>

Before

Width:  |  Height:  |  Size: 527 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 3 2"><path fill="#EC1920" d="M0 0h3v2H0z"/><path fill="#fff" d="M0 0h2v2H0z"/><path fill="#051440" d="M0 0h1v2H0z"/></svg>

Before

Width:  |  Height:  |  Size: 175 B

View File

@@ -1,28 +0,0 @@
{
"hero": {
"title": "Welcome to MyLinks",
"cta": "Get started"
},
"category": {
"title": "Create categories",
"text": "Organize your bookmarks by categories to keep your links tidy and find them easily."
},
"link": {
"title": "Manage Links",
"text": "Add, edit, and manage your bookmarks with a simple and intuitive interface."
},
"search": {
"title": "Search",
"text": "Quickly find the bookmark you're looking for with the powerful search feature."
},
"extension": {
"title": "Browser extension",
"text": "Enhance your experience with the official MyLinks browser extension."
},
"contribute": {
"title": "Contribute to MyLinks",
"text": "Suggest improvements you would like to see on MyLinks."
},
"look-title": "Take a look",
"website-screenshot-alt": "A screenshot of MyLinks"
}

View File

@@ -1,9 +0,0 @@
{
"role": "Role",
"created_at": "Created at",
"updated_at": "Updated at",
"admin": "Administrator",
"user": "User",
"users": "Users",
"stats": "Statistics"
}

View File

@@ -1,53 +0,0 @@
{
"slogan": "Manage your links in the best possible way",
"confirm": "Confirm",
"cancel": "Cancel",
"back-home": "← Back to home page",
"logout": "Logout",
"login": "Login",
"link": {
"links": "Links",
"link": "Link",
"name": "Link name",
"description": "Link description",
"create": "Create a link",
"edit": "Edit a link",
"remove": "Delete a link",
"remove-confirm": "Confirm deletion?"
},
"category": {
"categories": "Categories",
"category": "Category",
"name": "Category name",
"description": "Category description",
"no-description": "No description",
"visibility": "Public",
"create": "Create a category",
"edit": "Edit a category",
"remove": "Delete a category",
"remove-confirm": "Confirm deletion?",
"remove-description": "You must delete all links in this category before you can delete this category."
},
"favorite": "Favorite",
"no-item-found": "No item found",
"search": "Search",
"avatar": "{{name}}'s avatar",
"generic-error": "Something went wrong",
"generic-error-description": "An error has occurred, if this happens again please <a href=\"https://github.com/Sonny93/my-links\" target=\"_blank\">create an issue</a> with as much detail as possible.",
"retry": "Retry",
"privacy": "Privacy",
"terms": "Terms of use",
"language": {
"fr": "Français",
"en": "English"
},
"lang": "Language",
"settings": "Settings",
"profile": "Profile",
"select-your-lang": "Change the language",
"name": "Name",
"email": "Email",
"footer": {
"made_by": "Made with ❤\uFE0F by"
}
}

View File

@@ -1,5 +0,0 @@
{
"select-category": "Please select a category",
"or-create-one": "or create one",
"no-link": "No link for <b>{{name}}</b>"
}

View File

@@ -1,5 +0,0 @@
{
"title": "Authentication",
"informative-text": "Authentication required to use MyLinks",
"continue-with": "Continue with {{provider}}"
}

View File

@@ -1,45 +0,0 @@
{
"title": "Privacy Policy of MyLinks",
"edited_at": "Last updated: {{date}}",
"welcome": "Welcome to MyLinks, a free and open-source bookmark manager focused on privacy and self-hosting. This privacy policy aims to inform you about how we collect, use, and protect your data.",
"collect": {
"title": "1. Data Collection",
"cookie": {
"title": "1.1 Cookies",
"description": "Cookies used on MyLinks are essential to ensure the proper functioning of the site. By continuing to use our service, you consent to the use of these cookies."
},
"user": {
"title": "1.2 User Data",
"description": "To create personalized categories and links and associate them with their author, we collect the following information:",
"fields": ["Google ID", "Lastname", "Firstname", "Email", "Avatar"]
}
},
"data_use": {
"title": "2. Data Use",
"description": "The collected data is neither resold nor used for purposes other than initially intended, namely the management of categories and links created by the user."
},
"data_storage": {
"title": "3. Data Storage",
"description": "Data is stored securely to protect your privacy.",
"data_retention": {
"title": "3.1 Data Retention Period",
"description": "Functional data is retained until the user requests deletion. Once this request is made, the data will be permanently deleted."
}
},
"user_rights": {
"title": "4. User Rights",
"description": "The user has the right to retrieve all their data at any time and/or request the complete deletion of their data."
},
"gdpr": {
"title": "5. GDPR Compliance",
"description": "MyLinks complies with the General Data Protection Regulation (GDPR) of the European Union."
},
"contact": {
"title": "6. Contact",
"description": "If you have any questions or concerns about our privacy policy, feel free to contact us at the following address:"
},
"footer": {
"changes": "We reserve the right to update this privacy policy. We encourage you to regularly check this page to stay informed of any changes.",
"thanks": "Thank you for using MyLinks!"
}
}

View File

@@ -1,62 +0,0 @@
{
"title": "Terms and Conditions of Use for MyLinks",
"edited_at": "Last updated: {{date}}",
"welcome": "Welcome to MyLinks, a free and open-source bookmark manager focused on privacy and self-hosting. By using this service, you agree to the terms and conditions of use outlined below. Please read them carefully.",
"accept": {
"title": "1. Acceptance of Terms",
"description": "By accessing MyLinks and using our services, you agree to comply with these Terms and Conditions of Use."
},
"use": {
"title": "2. Use of the Service",
"account": {
"title": "2.1 User Account",
"description": "To access certain features of MyLinks, you will need to create a user account. You are responsible for the confidentiality of your account and credentials."
},
"allowed": {
"title": "2.2 Authorized Use",
"description": "You commit to using MyLinks in accordance with applicable laws and not violating the rights of third parties."
},
"user_content": {
"title": "2.3 User Content",
"description": "By posting content on MyLinks, you grant MyLinks a worldwide, non-exclusive, transferable, and free license to use, reproduce, distribute, and display this content."
}
},
"personal_data": {
"title": "3. Personal Data",
"collect": {
"title": "3.1 Collection and Use",
"description": "The personal data collected is used in accordance with our <a>Privacy Policy</a>. By using MyLinks, you consent to this collection and use."
},
"suppress": {
"title": "3.2 Account Deletion",
"description": "You can request the deletion of your account at any time in accordance with our Privacy Policy."
}
},
"responsibility_warranty": {
"title": "4. Responsibilities and Warranties",
"responsibility": {
"title": "4.1 Responsibility",
"description": "MyLinks cannot be held responsible for direct or indirect damages arising from the use of our services."
},
"warranty": {
"title": "4.2 Warranties",
"description": "MyLinks does not guarantee that the service will be free from errors or interruptions."
}
},
"terms_changes": {
"title": "5. Changes to the Terms",
"description": "MyLinks reserves the right to modify these Terms and Conditions of Use at any time. Users will be notified of changes through a notification on the site."
},
"cancel": {
"title": "6. Termination",
"description": "MyLinks reserves the right to terminate or suspend your access to the service, with or without notice, in case of violation of these Terms and Conditions of Use."
},
"contact": {
"title": "7. Contact",
"description": "For any questions or concerns regarding these Terms and Conditions of Use, please contact us at the following address:"
},
"footer": {
"changes": "We reserve the right to update these Terms and Conditions of Use. We encourage you to regularly check this page to stay informed of any changes.",
"thanks": "Thank you for using MyLinks!"
}
}

View File

@@ -1,28 +0,0 @@
{
"hero": {
"title": "Bienvenue sur MyLinks",
"cta": "Lancez-vous !"
},
"category": {
"title": "Créer des catégories",
"text": "Organisez vos favoris dans des catégories pour garder vos liens en ordre et les retrouver facilement."
},
"link": {
"title": "Gérer les liens",
"text": "Ajoutez, modifiez et gérez vos favoris à l'aide d'une interface simple et intuitive."
},
"search": {
"title": "Rechercher",
"text": "Trouvez rapidement vos liens favoris en utilisant la fonction de recherche."
},
"extension": {
"title": "Extension de navigateur",
"text": "Améliorez votre expérience avec l'extension de navigateur officielle MyLinks."
},
"contribute": {
"title": "Contribuer à MyLinks",
"text": "Proposez des améliorations que vous souhaiteriez voir sur MyLinks."
},
"look-title": "Jetez un coup d'oeil",
"website-screenshot-alt": "Une capture d'écran de MyLinks"
}

View File

@@ -1,9 +0,0 @@
{
"role": "Rôle",
"created_at": "Création",
"updated_at": "Mise à jour",
"admin": "Administrateur",
"user": "Utilisateur",
"users": "Utilisateurs",
"stats": "Statistiques"
}

View File

@@ -1,53 +0,0 @@
{
"slogan": "Gérez vos liens de la meilleure des façons",
"confirm": "Confirmer",
"cancel": "Annuler",
"back-home": "← Revenir à l'accueil",
"logout": "Déconnexion",
"login": "Connexion",
"link": {
"links": "Liens",
"link": "Lien",
"name": "Nom du lien",
"description": "Description du lien",
"create": "Créer un lien",
"edit": "Modifier un lien",
"remove": "Supprimer un lien",
"remove-confirm": "Confirmer la suppression ?"
},
"category": {
"categories": "Catégories",
"category": "Catégorie",
"name": "Nom de la catégorie",
"description": "Description de la catégorie",
"visibility": "Public",
"no-description": "Aucune description",
"create": "Créer une catégorie",
"edit": "Modifier une catégorie",
"remove": "Supprimer une catégorie",
"remove-confirm": "Confirmer la suppression ?",
"remove-description": "Vous devez supprimer tous les liens de cette catégorie avant de pouvoir supprimer cette catégorie"
},
"favorite": "Favoris",
"no-item-found": "Aucun élément trouvé",
"search": "Rechercher",
"avatar": "Avatar de {{name}}",
"generic-error": "Une erreur est survenue",
"generic-error-description": "Une erreur est survenue, si cela se reproduit merci de <a href=\"https://github.com/Sonny93/my-links\" target=\"_blank\">créer une issue</a> avec le maximum de détails.",
"retry": "Recommencer",
"privacy": "Confidentialité",
"terms": "CGU",
"language": {
"fr": "Français",
"en": "English"
},
"lang": "Langage",
"settings": "Paramètres",
"profile": "Profil",
"select-your-lang": "Modifier la langue",
"name": "Nom",
"email": "Email",
"footer": {
"made_by": "Fait avec ❤\uFE0F par"
}
}

View File

@@ -1,5 +0,0 @@
{
"select-category": "Veuillez sélectionner une categories",
"or-create-one": "ou en créer une",
"no-link": "Aucun lien pour <b>{{name}}</b>"
}

View File

@@ -1,5 +0,0 @@
{
"title": "Authentification",
"informative-text": "Authentification requise pour utiliser MyLinks",
"continue-with": "Continuer avec {{provider}}"
}

View File

@@ -1,51 +0,0 @@
{
"title": "Politique de confidentialité de MyLinks",
"edited_at": "Dernière mise à jour : {{date}}",
"welcome": "Bienvenue sur MyLinks, un gestionnaire de favoris gratuit et open source axé sur la privacy et le self hosting. Cette politique de confidentialité vise à vous informer sur la manière dont nous collectons, utilisons et protégeons vos données.",
"collect": {
"title": "1. Collecte de données",
"cookie": {
"title": "1.1 Cookies",
"description": "Les cookies utilisés sur MyLinks sont indispensables pour assurer le bon fonctionnement du site. En continuant à utiliser notre service, vous consentez à l'utilisation de ces cookies."
},
"user": {
"title": "1.2 Données utilisateur",
"description": "Pour créer des catégories et liens personnalisés et les associer à leur auteur, nous collectons les informations suivantes :",
"fields": [
"Identifiant Google",
"Nom",
"Prénom",
"Adresse e-mail",
"Avatar"
]
}
},
"data_use": {
"title": "2. Utilisation des données",
"description": "Les données collectées ne sont ni revendues ni utilisées à d'autres fins que celles prévues initialement, à savoir la gestion des catégories et des liens créés par l'utilisateur."
},
"data_storage": {
"title": "3. Stockage des données",
"description": "Les données sont stockées de manière sécurisée afin de protéger votre confidentialité.",
"data_retention": {
"title": "3.1 Durée de conservation",
"description": "Les données fonctionnelles sont conservées jusqu'à ce que l'utilisateur fasse une demande de suppression. Une fois cette demande effectuée, les données seront définitivement supprimées."
}
},
"user_rights": {
"title": "4. Droits de l'utilisateur",
"description": "L'utilisateur a le droit de récupérer l'ensemble de ses données à tout moment et/ou de demander la suppression complète de ses données."
},
"gdpr": {
"title": "5. Conformité au RGPD",
"description": "MyLinks est conforme au Règlement Général sur la Protection des Données (RGPD) de l'Union européenne."
},
"contact": {
"title": "6. Contact",
"description": "Si vous avez des questions ou des préoccupations concernant notre politique de confidentialité, n'hésitez pas à nous contacter à l'adresse suivante :"
},
"footer": {
"changes": "Nous nous réservons le droit de mettre à jour cette politique de confidentialité. Nous vous encourageons à consulter régulièrement cette page pour rester informé des changements éventuels.",
"thanks": "Merci d'utiliser MyLinks !"
}
}

View File

@@ -1,62 +0,0 @@
{
"title": "Conditions Générales d'Utilisation de MyLinks",
"edited_at": "Dernière mise à jour : {{date}}",
"welcome": "Bienvenue sur MyLinks, un gestionnaire de favoris gratuit et open source axé sur la privacy et le self hosting. En utilisant ce service, vous acceptez les conditions générales d'utilisation énoncées ci-dessous. Veuillez les lire attentivement.",
"accept": {
"title": "1. Acceptation des Conditions",
"description": "En accédant à MyLinks et en utilisant nos services, vous acceptez de vous conformer à ces Conditions Générales d'Utilisation."
},
"use": {
"title": "2. Utilisation du Service",
"account": {
"title": "2.1 Compte Utilisateur",
"description": "Pour accéder à certaines fonctionnalités de MyLinks, vous devrez créer un compte utilisateur. Vous êtes responsable de la confidentialité de votre compte et de vos informations d'identification."
},
"allowed": {
"title": "2.2 Utilisation Autorisée",
"description": "Vous vous engagez à utiliser MyLinks conformément aux lois en vigueur et à ne pas violer les droits de tiers."
},
"user_content": {
"title": "2.3 Contenu Utilisateur",
"description": "En publiant du contenu sur MyLinks, vous accordez à MyLinks une licence mondiale, non exclusive, transférable et gratuite pour utiliser, reproduire, distribuer et afficher ce contenu."
}
},
"personal_data": {
"title": "3. Données Personnelles",
"collect": {
"title": "3.1 Collecte et Utilisation",
"description": "Les données personnelles collectées sont utilisées conformément à notre <a>Politique de Confidentialité</a>. En utilisant MyLinks, vous consentez à cette collecte et utilisation."
},
"suppress": {
"title": "3.2 Suppression de Compte",
"description": "Vous pouvez demander la suppression de votre compte à tout moment conformément à notre Politique de Confidentialité."
}
},
"responsibility_warranty": {
"title": "4. Responsabilités et Garanties",
"responsibility": {
"title": "4.1 Responsabilité",
"description": "MyLinks ne peut être tenu responsable des dommages directs ou indirects découlant de l'utilisation de nos services."
},
"warranty": {
"title": "4.2 Garanties",
"description": "MyLinks ne garantit pas que le service sera exempt d'erreurs ou de interruptions."
}
},
"terms_changes": {
"title": "5. Modifications des Conditions",
"description": "MyLinks se réserve le droit de modifier ces Conditions Générales\n d'Utilisation à tout moment. Les utilisateurs seront informés des\n changements par le biais d'une notification sur le site."
},
"cancel": {
"title": "6. Résiliation",
"description": "MyLinks se réserve le droit de résilier ou de suspendre votre accès au service, avec ou sans préavis, en cas de violation de ces Conditions Générales d'Utilisation."
},
"contact": {
"title": "7. Contact",
"description": "Pour toute question ou préoccupation concernant ces Conditions Générales d'Utilisation, veuillez nous contacter à l'adresse suivante :"
},
"footer": {
"changes": "Nous nous réservons le droit de mettre à jour ces Conditions Générales d'Utilisation. Nous vous encourageons à consulter régulièrement cette page pour rester informé des changements éventuels.",
"thanks": "Merci d'utiliser MyLinks !"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

View File

@@ -1,47 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<svg viewBox="0 0 500 165" xmlns="http://www.w3.org/2000/svg">
<g id="tight-bounds" transform="matrix(1.264159, 0, 0, 1.265787, 0, 0)">
<svg viewBox="0 0 395.52 130.3536553275838" height="130.3536553275838" width="395.52">
<g>
<svg viewBox="0 0 609.8756479073379 200.99999999999997" height="130.3536553275838" width="395.52">
<g>
<rect width="8.33878379307346" height="200.99999999999997" x="222.27426506352492" y="0" stroke-width="0" fill-opacity="1" class="rect-o-0" rx="1%" id="o-0" style="fill: rgb(72, 162, 255);" stroke="transparent"/>
</g>
<g transform="matrix(1,0,0,1,252.44232580181685,0.4999999999999858)">
<svg viewBox="0 0 357.43332210552103 200" height="200" width="357.43332210552103">
<g>
<svg viewBox="0 0 357.43332210552103 200" height="200" width="357.43332210552103">
<g>
<svg viewBox="0 0 357.43332210552103 200" height="200" width="357.43332210552103">
<g transform="matrix(1,0,0,1,0,0)">
<svg width="357.43332210552103" viewBox="3.7404773235321045 -36.008331298828125 142.35955810546875 76.29547882080078" height="200" data-palette-color="#005aa5">
<svg/>
<svg/>
<g>
<path xmlns="http://www.w3.org/2000/svg" d="M39.3-3.35v0c-0.033 0.967-0.4 1.783-1.1 2.45-0.7 0.667-1.533 0.983-2.5 0.95-0.967-0.033-1.783-0.4-2.45-1.1-0.667-0.7-0.983-1.533-0.95-2.5v0c0.067-2.167 0.033-8.567-0.1-19.2v0c-2.467 3.1-4.9 6.417-7.3 9.95v0c-0.567 0.8-1.317 1.29-2.25 1.47-0.933 0.187-1.8 0.013-2.6-0.52v0c-0.1-0.067-0.183-0.133-0.25-0.2v0c-0.367-0.233-0.683-0.517-0.95-0.85v0l-7.7-9.7c0.033 0.067 0.043 3.257 0.03 9.57-0.02 6.32-0.03 9.497-0.03 9.53v0c0 0.967-0.34 1.79-1.02 2.47-0.687 0.687-1.513 1.03-2.48 1.03-0.967 0-1.79-0.343-2.47-1.03-0.687-0.68-1.03-1.503-1.03-2.47v0c0-1.4 0.007-3.75 0.02-7.05 0.02-3.3 0.03-5.85 0.03-7.65 0-1.8-0.033-4.033-0.1-6.7v0c-0.067-2.367-0.183-4.817-0.35-7.35v0c-0.067-0.967 0.217-1.817 0.85-2.55 0.633-0.733 1.433-1.133 2.4-1.2v0c0.5-0.033 0.967 0.033 1.4 0.2v0c0.933 0.133 1.683 0.583 2.25 1.35v0l11.1 13.95c3.6-5 7.267-9.5 11-13.5v0c0.7-1.2 1.733-1.783 3.1-1.75v0c0.967 0.033 1.783 0.4 2.45 1.1 0.667 0.7 0.983 1.533 0.95 2.5v0c-0.1 3.167-0.1 8.007 0 14.52 0.1 6.52 0.117 11.28 0.05 14.28zM54.6-3.3v0c0-0.833-0.007-1.977-0.02-3.43-0.02-1.447-0.03-2.587-0.03-3.42v0-2.85l-5-8.3c-2.467-4.033-4.3-7.183-5.5-9.45v0c-0.467-0.833-0.567-1.71-0.3-2.63 0.267-0.913 0.827-1.603 1.68-2.07 0.847-0.467 1.73-0.567 2.65-0.3 0.913 0.267 1.603 0.833 2.07 1.7v0c1.1 1.967 3.6 6.183 7.5 12.65v0l2.7-4.2 5.7-8.6c0.5-0.8 1.217-1.317 2.15-1.55 0.933-0.233 1.81-0.093 2.63 0.42 0.813 0.52 1.337 1.247 1.57 2.18 0.233 0.933 0.083 1.817-0.45 2.65v0c-0.2 0.333-2.133 3.267-5.8 8.8v0c-1.967 2.967-3.517 5.45-4.65 7.45v0c0.033 0.767 0.05 2.45 0.05 5.05 0 2.6 0.017 4.5 0.05 5.7v0c0.033 0.967-0.283 1.8-0.95 2.5-0.667 0.7-1.483 1.067-2.45 1.1-0.967 0.033-1.8-0.283-2.5-0.95-0.7-0.667-1.067-1.483-1.1-2.45z" fill-rule="nonzero" stroke-width="1" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="10" stroke-dasharray="" stroke-dashoffset="0" style="mix-blend-mode: normal; fill: rgb(72, 162, 255);" stroke="none"/>
<path xmlns="http://www.w3.org/2000/svg" d="M7.9 39.93v0c-1.133-0.033-2.05-0.5-2.75-1.4v0c-0.667-0.667-1.017-1.483-1.05-2.45v0c-0.033-3.4-0.1-8.217-0.2-14.45v0l-0.15-14.05c0-0.967 0.333-1.793 1-2.48 0.667-0.68 1.483-1.027 2.45-1.04 0.967-0.02 1.793 0.303 2.48 0.97 0.68 0.667 1.037 1.483 1.07 2.45v0l0.15 13.1 0.15 12.6c1.633 0 4.01-0.017 7.13-0.05 3.113-0.033 5.403-0.05 6.87-0.05v0c0.967 0 1.793 0.323 2.48 0.97 0.68 0.653 1.02 1.447 1.02 2.38 0 0.933-0.34 1.74-1.02 2.42-0.687 0.687-1.513 1.03-2.48 1.03v0c-1.1 0-3.95 0.017-8.55 0.05v0c-4.633 0.033-7.5 0.033-8.6 0zM34.5 39.98v0c-0.967 0-1.8-0.327-2.5-0.98-0.7-0.647-1.067-1.453-1.1-2.42v0c0-0.867 0.277-1.643 0.83-2.33 0.547-0.68 1.237-1.087 2.07-1.22v0c0.033-5.8 0.017-13.15-0.05-22.05v0c-0.8-0.167-1.467-0.567-2-1.2-0.533-0.633-0.8-1.383-0.8-2.25v0c0-0.967 0.35-1.783 1.05-2.45 0.7-0.667 1.533-1 2.5-1v0l5.65 0.05c0.967 0 1.783 0.35 2.45 1.05 0.667 0.7 1 1.533 1 2.5v0c0 0.833-0.273 1.567-0.82 2.2-0.553 0.633-1.247 1.033-2.08 1.2v0c0.1 8.767 0.133 16.033 0.1 21.8v0c0.833 0.133 1.527 0.517 2.08 1.15 0.547 0.633 0.837 1.367 0.87 2.2v0c0 0.967-0.323 1.8-0.97 2.5-0.653 0.7-1.463 1.067-2.43 1.1v0zM76.15 39.63v0c-1.3 0.233-2.383-0.167-3.25-1.2v0c-2.233-2.633-5.05-6.533-8.45-11.7v0c-3.7-5.233-6.317-8.783-7.85-10.65v0c0.033 1.333 0.117 4.29 0.25 8.87 0.133 4.587 0.2 8.397 0.2 11.43v0c0 0.967-0.34 1.79-1.02 2.47-0.687 0.687-1.513 1.03-2.48 1.03-0.967 0-1.79-0.343-2.47-1.03-0.687-0.68-1.03-1.503-1.03-2.47v0c0-2.933-0.093-7.677-0.28-14.23-0.18-6.547-0.203-11.503-0.07-14.87v0c0.033-0.967 0.4-1.783 1.1-2.45 0.7-0.667 1.533-0.983 2.5-0.95v0c0.467 0.033 0.917 0.133 1.35 0.3v0c0.633 0.2 1.183 0.567 1.65 1.1v0c0.567 0.7 1.483 1.807 2.75 3.32 1.267 1.52 2.217 2.663 2.85 3.43v0c1.867 1.933 4.633 5.5 8.3 10.7v0l2.4 3.45c0.1-7.733 0.117-14.017 0.05-18.85v0c0-0.967 0.333-1.793 1-2.48 0.667-0.68 1.483-1.027 2.45-1.04 0.967-0.02 1.793 0.303 2.48 0.97 0.68 0.667 1.037 1.483 1.07 2.45v0c0.033 4.533-0.017 14.183-0.15 28.95v0c-0.033 0.933-0.367 1.723-1 2.37-0.633 0.653-1.417 1.013-2.35 1.08zM109.85 38.83l-11.25-14.45-4.2 3.45c0.1 2.7 0.217 5.617 0.35 8.75v0c0.033 0.933-0.273 1.757-0.92 2.47-0.653 0.72-1.453 1.107-2.4 1.16-0.953 0.047-1.787-0.257-2.5-0.91-0.72-0.647-1.113-1.453-1.18-2.42v0c-0.5-10.267-0.683-20.267-0.55-30v0c0.033-0.967 0.393-1.783 1.08-2.45 0.68-0.667 1.503-0.993 2.47-0.98 0.967 0.02 1.783 0.37 2.45 1.05 0.667 0.687 1 1.513 1 2.48v0c-0.067 4.233-0.067 8.217 0 11.95v0c5.233-4.467 10.083-9.217 14.55-14.25v0c0.667-0.7 1.477-1.067 2.43-1.1 0.947-0.033 1.78 0.283 2.5 0.95 0.713 0.667 1.087 1.473 1.12 2.42 0.033 0.953-0.283 1.797-0.95 2.53v0c-3.267 3.733-6.6 7.15-10 10.25v0l11.5 14.8c0.6 0.733 0.833 1.583 0.7 2.55v0c-0.1 0.967-0.523 1.75-1.27 2.35-0.753 0.6-1.613 0.843-2.58 0.73-0.967-0.12-1.75-0.563-2.35-1.33zM132.85 40.28v0c-2.567 0.067-4.883-0.333-6.95-1.2-2.067-0.867-3.7-2.133-4.9-3.8-1.2-1.667-1.8-3.6-1.8-5.8v0c0-1.333 0.383-2.377 1.15-3.13 0.767-0.747 1.633-1.12 2.6-1.12v0c0.967 0 1.75 0.34 2.35 1.02 0.6 0.687 0.9 1.447 0.9 2.28v0c0 0.833 0.05 1.527 0.15 2.08 0.1 0.547 0.35 1.053 0.75 1.52v0c0.767 0.933 2.717 1.4 5.85 1.4v0c1.7 0 3.15-0.35 4.35-1.05 1.2-0.7 1.8-1.7 1.8-3v0c0-0.867-0.7-1.7-2.1-2.5v0c-0.967-0.533-3.117-1.367-6.45-2.5v0c-3.633-1.2-6.367-2.593-8.2-4.18-1.833-1.58-2.75-3.737-2.75-6.47v0c0-2.1 0.6-3.933 1.8-5.5 1.2-1.567 2.777-2.767 4.73-3.6 1.947-0.833 4.02-1.25 6.22-1.25v0c2.267-0.067 4.45 0.3 6.55 1.1 2.1 0.8 3.793 1.94 5.08 3.42 1.28 1.487 1.92 3.163 1.92 5.03v0c0 1-0.367 1.857-1.1 2.57-0.733 0.72-1.583 1.08-2.55 1.08v0c-0.667 0-1.317-0.2-1.95-0.6-0.633-0.4-1.033-0.9-1.2-1.5v0c-0.2-0.8-0.49-1.417-0.87-1.85-0.387-0.433-0.897-0.833-1.53-1.2v0c-0.833-0.467-1.583-0.8-2.25-1-0.667-0.2-1.617-0.3-2.85-0.3v0c-1.467 0-2.673 0.273-3.62 0.82-0.953 0.553-1.43 1.263-1.43 2.13v0c0 1.1 0.543 2 1.63 2.7 1.08 0.7 2.603 1.367 4.57 2v0c3.8 1.267 6.333 2.25 7.6 2.95v0c2.1 1.167 3.583 2.4 4.45 3.7 0.867 1.3 1.3 2.933 1.3 4.9v0c0 2.1-0.633 3.973-1.9 5.62-1.267 1.653-2.923 2.937-4.97 3.85-2.053 0.92-4.18 1.38-6.38 1.38z" fill-rule="nonzero" stroke-width="1" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="10" stroke-dasharray="" stroke-dashoffset="0" style="mix-blend-mode: normal; fill: rgb(72, 162, 255);" stroke="none"/>
</g>
</svg>
</g>
</svg>
</g>
</svg>
</g>
</svg>
</g>
<g transform="matrix(1,0,0,1,0,0.5)">
<svg viewBox="0 0 200.44498811830644 199.99999999999997" height="199.99999999999997" width="200.44498811830644">
<g>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" x="0" y="0" viewBox="0 0.11100006103515625 100 99.77799987792969" enable-background="new 0 0 100 100" height="199.99999999999997" width="200.44498811830644" class="icon-icon-0" data-fill-palette-color="accent" id="icon-0">
<path d="M53.406 36.706L43.25 36.707c0 5.521 4.547 9.999 10.156 9.999 9.07 0 16.592 7.522 16.592 16.592H70c0 9.07-7.522 16.592-16.592 16.592H0l31 19.999v-5c0-2.762 2.238-5 5-5h17.408v0.003C68.07 89.892 80 77.962 80 63.298 80 48.636 68.07 36.706 53.406 36.706z" style="fill: rgb(72, 162, 255);"/>
<path d="M46.592 63.294l10.157-0.001c0-5.521-4.548-9.999-10.157-9.999C37.521 53.294 30 45.771 30 36.702c0-9.07 7.522-16.591 16.592-16.591H100l-31-20v5c0 2.762-2.238 5-5 5H46.592v-0.003C31.93 10.108 20 22.038 20 36.702h-0.002C19.998 51.364 31.928 63.294 46.592 63.294z" style="fill: rgb(72, 162, 255);"/>
</svg>
</g>
</svg>
</g>
</svg>
</g>
</svg>
<rect width="395.52" height="130.3536553275838" fill="none" stroke="none" visibility="hidden"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 9.0 KiB

View File

@@ -1,24 +0,0 @@
{
"name": "MyLinks",
"short_name": "MyLinks",
"description": "MyLinks is a free and open source software, that lets you manage your favorite links in an intuitive interface",
"launch_handler": {
"client_mode": [
"focus-existing",
"auto"
]
},
"icons": [
{
"src": "/favicon.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any maskable"
}
],
"theme_color": "#f0eef6",
"background_color": "#f0eef6",
"start_url": "/",
"display": "standalone",
"orientation": "portrait"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Some files were not shown because too many files have changed in this diff Show More