mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-08-02 20:15:24 +02:00
feature/multi-tenancy and move caddy server (#980)
* update to GUIDs * fix cookbook id relationships * update webhook keys * cleanup naming and attribute orders * remove old database tables * fix meal-plan images * remove dashbaord and events api * use recipe-id instead of id * cleanup documentation assets * cleanup docs for v1 beta-release * add depends_on for docker-compose * use docker volumes for examples * move caddy to frontend container
This commit is contained in:
parent
14cc541f7a
commit
602f248541
91 changed files with 187 additions and 1170 deletions
|
@ -3,8 +3,8 @@
|
|||
admin off
|
||||
}
|
||||
|
||||
:80 {
|
||||
@proxied path /api/* /docs /openapi.json
|
||||
:3000 {
|
||||
@apidocs path /docs /openapi.json
|
||||
|
||||
@static {
|
||||
file
|
||||
|
@ -20,15 +20,21 @@
|
|||
file_server
|
||||
}
|
||||
|
||||
handle @proxied {
|
||||
# Handles User Images
|
||||
handle_path /api/media/users/* {
|
||||
header @static Cache-Control max-age=31536000
|
||||
root * /app/data/users/
|
||||
file_server
|
||||
}
|
||||
|
||||
|
||||
handle @apidocs {
|
||||
uri strip_suffix /
|
||||
reverse_proxy http://mealie-api
|
||||
reverse_proxy http://mealie-api:9000
|
||||
}
|
||||
|
||||
handle {
|
||||
header @static Cache-Control max-age=31536000
|
||||
root * /app/dist
|
||||
try_files {path}.html {path} /
|
||||
file_server
|
||||
uri strip_suffix /
|
||||
reverse_proxy http://127.0.0.1:3001
|
||||
}
|
||||
}
|
||||
}
|
|
@ -23,12 +23,16 @@ RUN rm -rf node_modules && \
|
|||
|
||||
FROM node:16-alpine
|
||||
|
||||
RUN apk add caddy
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# copying caddy into image
|
||||
COPY --from=builder /app .
|
||||
COPY ./Caddyfile /app/
|
||||
|
||||
ENV HOST 0.0.0.0
|
||||
EXPOSE 3000
|
||||
|
||||
CMD [ "yarn", "start" ]
|
||||
RUN chmod +x /app/run.sh
|
||||
ENTRYPOINT /app/run.sh
|
||||
|
|
|
@ -1,49 +0,0 @@
|
|||
import { BaseAPI } from "../_base";
|
||||
|
||||
export type EventCategory = "general" | "recipe" | "backup" | "scheduled" | "migration" | "group" | "user";
|
||||
|
||||
export interface Event {
|
||||
id?: number;
|
||||
title: string;
|
||||
text: string;
|
||||
timeStamp?: string;
|
||||
category?: EventCategory & string;
|
||||
}
|
||||
|
||||
export interface EventsOut {
|
||||
total: number;
|
||||
events: Event[];
|
||||
}
|
||||
|
||||
const prefix = "/api";
|
||||
|
||||
const routes = {
|
||||
aboutEvents: `${prefix}/about/events`,
|
||||
aboutEventsNotifications: `${prefix}/about/events/notifications`,
|
||||
aboutEventsNotificationsTest: `${prefix}/about/events/notifications/test`,
|
||||
|
||||
aboutEventsId: (id: number) => `${prefix}/about/events/${id}`,
|
||||
aboutEventsNotificationsId: (id: number) => `${prefix}/about/events/notifications/${id}`,
|
||||
};
|
||||
|
||||
export class EventsAPI extends BaseAPI {
|
||||
/** Get event from the Database
|
||||
*/
|
||||
async getEvents() {
|
||||
return await this.requests.get<EventsOut>(routes.aboutEvents);
|
||||
}
|
||||
|
||||
/** Get event from the Database
|
||||
*/
|
||||
async deleteEvents() {
|
||||
return await this.requests.delete(routes.aboutEvents);
|
||||
}
|
||||
|
||||
/** Delete event from the Database
|
||||
*/
|
||||
async deleteEvent(id: number) {
|
||||
return await this.requests.delete(routes.aboutEventsId(id));
|
||||
}
|
||||
/** Get all event_notification from the Database
|
||||
*/
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
import { RecipeAPI } from "./class-interfaces/recipes";
|
||||
import { UserApi } from "./class-interfaces/users";
|
||||
import { GroupAPI } from "./class-interfaces/groups";
|
||||
import { EventsAPI } from "./class-interfaces/events";
|
||||
import { BackupAPI } from "./class-interfaces/backups";
|
||||
import { UploadFile } from "./class-interfaces/upload";
|
||||
import { CategoriesAPI } from "./class-interfaces/organizer-categories";
|
||||
|
@ -30,7 +29,6 @@ class Api {
|
|||
public recipes: RecipeAPI;
|
||||
public users: UserApi;
|
||||
public groups: GroupAPI;
|
||||
public events: EventsAPI;
|
||||
public backups: BackupAPI;
|
||||
public categories: CategoriesAPI;
|
||||
public tags: TagsAPI;
|
||||
|
@ -79,7 +77,6 @@ class Api {
|
|||
this.multiPurposeLabels = new MultiPurposeLabelsApi(requests);
|
||||
|
||||
// Admin
|
||||
this.events = new EventsAPI(requests);
|
||||
this.backups = new BackupAPI(requests);
|
||||
|
||||
// Utils
|
||||
|
|
|
@ -24,7 +24,6 @@
|
|||
</v-app>
|
||||
</template>
|
||||
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, useContext, onMounted } from "@nuxtjs/composition-api";
|
||||
import AppHeader from "@/components/Layout/AppHeader.vue";
|
||||
|
@ -45,11 +44,11 @@ export default defineComponent({
|
|||
});
|
||||
|
||||
const topLinks: SidebarLinks = [
|
||||
{
|
||||
icon: $globals.icons.viewDashboard,
|
||||
to: "/admin/dashboard",
|
||||
title: i18n.t("sidebar.dashboard"),
|
||||
},
|
||||
// {
|
||||
// icon: $globals.icons.viewDashboard,
|
||||
// to: "/admin/dashboard",
|
||||
// title: i18n.t("sidebar.dashboard"),
|
||||
// },
|
||||
{
|
||||
icon: $globals.icons.cog,
|
||||
to: "/admin/site-settings",
|
||||
|
@ -115,6 +114,3 @@ export default defineComponent({
|
|||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -142,7 +142,7 @@ export default defineComponent({
|
|||
{
|
||||
icon: this.$globals.icons.cog,
|
||||
title: this.$t("general.settings"),
|
||||
to: "/admin/dashboard",
|
||||
to: "/admin/site-settings",
|
||||
restricted: true,
|
||||
},
|
||||
],
|
||||
|
|
|
@ -1,158 +0,0 @@
|
|||
<template>
|
||||
<v-container v-if="statistics" class="mt-10">
|
||||
<v-row v-if="statistics">
|
||||
<v-col cols="12" sm="12" md="4">
|
||||
<BaseStatCard :icon="$globals.icons.primary">
|
||||
<template #after-heading>
|
||||
<div class="ml-auto text-right">
|
||||
<h2 class="body-3 grey--text font-weight-light">
|
||||
{{ $t("general.recipes") }}
|
||||
</h2>
|
||||
|
||||
<h3 class="display-2 font-weight-light text--primary">
|
||||
<small> {{ statistics.totalRecipes }}</small>
|
||||
</h3>
|
||||
</div>
|
||||
</template>
|
||||
<template #actions>
|
||||
<div class="d-flex row py-2 justify-end">
|
||||
<v-btn class="ma-1" small color="primary" to="/admin/toolbox/organize">
|
||||
<v-icon left> {{ $globals.icons.tags }} </v-icon>
|
||||
{{ $tc("tag.untagged-count", [statistics.untaggedRecipes]) }}
|
||||
</v-btn>
|
||||
<v-btn class="ma-1" small color="primary" to="/admin/toolbox/organize">
|
||||
<v-icon left> {{ $globals.icons.tags }} </v-icon>
|
||||
{{ $tc("category.uncategorized-count", [statistics.uncategorizedRecipes]) }}
|
||||
</v-btn>
|
||||
</div>
|
||||
</template>
|
||||
</BaseStatCard>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="12" md="4">
|
||||
<BaseStatCard :icon="$globals.icons.user">
|
||||
<template #after-heading>
|
||||
<div class="ml-auto text-right">
|
||||
<h2 class="body-3 grey--text font-weight-light">
|
||||
{{ $t("user.users") }}
|
||||
</h2>
|
||||
<h3 class="display-2 font-weight-light text--primary">
|
||||
<small> {{ statistics.totalUsers }}</small>
|
||||
</h3>
|
||||
</div>
|
||||
</template>
|
||||
<template #actions>
|
||||
<div class="ml-auto">
|
||||
<v-btn color="primary" small to="/admin/manage/users">
|
||||
<v-icon left>{{ $globals.icons.user }}</v-icon>
|
||||
{{ $t("user.manage-users") }}
|
||||
</v-btn>
|
||||
</div>
|
||||
</template>
|
||||
</BaseStatCard>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="12" md="4">
|
||||
<BaseStatCard :icon="$globals.icons.group">
|
||||
<template #after-heading>
|
||||
<div class="ml-auto text-right">
|
||||
<h2 class="body-3 grey--text font-weight-light">
|
||||
{{ $t("group.groups") }}
|
||||
</h2>
|
||||
|
||||
<h3 class="display-2 font-weight-light text--primary">
|
||||
<small> {{ statistics.totalGroups }}</small>
|
||||
</h3>
|
||||
</div>
|
||||
</template>
|
||||
<template #actions>
|
||||
<div class="ml-auto">
|
||||
<v-btn color="primary" small to="/admin/manage/groups">
|
||||
<v-icon left>{{ $globals.icons.group }}</v-icon>
|
||||
{{ $t("group.manage-groups") }}
|
||||
</v-btn>
|
||||
</div>
|
||||
</template>
|
||||
</BaseStatCard>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row class="mt-10" align-content="stretch">
|
||||
<v-col>
|
||||
<AdminEventViewer
|
||||
v-if="events"
|
||||
:events="events.events"
|
||||
:total="events.total"
|
||||
@delete-all="deleteEvents"
|
||||
@delete-item="deleteEvent"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, useAsync } from "@nuxtjs/composition-api";
|
||||
import AdminEventViewer from "@/components/Domain/Admin/AdminEventViewer.vue";
|
||||
import { useAdminApi, useUserApi } from "~/composables/api";
|
||||
import { useAsyncKey } from "~/composables/use-utils";
|
||||
|
||||
export default defineComponent({
|
||||
components: { AdminEventViewer },
|
||||
layout: "admin",
|
||||
setup() {
|
||||
const api = useUserApi();
|
||||
|
||||
const adminApi = useAdminApi();
|
||||
|
||||
function getStatistics() {
|
||||
const statistics = useAsync(async () => {
|
||||
const { data } = await adminApi.about.statistics();
|
||||
return data;
|
||||
}, useAsyncKey());
|
||||
|
||||
return statistics;
|
||||
}
|
||||
|
||||
function getEvents() {
|
||||
const events = useAsync(async () => {
|
||||
const { data } = await api.events.getEvents();
|
||||
return data;
|
||||
}, useAsyncKey());
|
||||
return events;
|
||||
}
|
||||
|
||||
async function refreshEvents() {
|
||||
const { data } = await api.events.getEvents();
|
||||
events.value = data;
|
||||
}
|
||||
|
||||
async function deleteEvent(id: number) {
|
||||
const { response } = await api.events.deleteEvent(id);
|
||||
|
||||
if (response && response.status === 200) {
|
||||
refreshEvents();
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteEvents() {
|
||||
const { response } = await api.events.deleteEvents();
|
||||
|
||||
if (response && response.status === 200) {
|
||||
refreshEvents();
|
||||
}
|
||||
}
|
||||
|
||||
const events = getEvents();
|
||||
const statistics = getStatistics();
|
||||
|
||||
return { statistics, events, deleteEvents, deleteEvent };
|
||||
},
|
||||
head() {
|
||||
return {
|
||||
title: this.$t("sidebar.dashboard") as string,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
|
@ -204,7 +204,7 @@
|
|||
<RecipeCard
|
||||
v-for="mealplan in plan.meals"
|
||||
:key="mealplan.id"
|
||||
:recipe-id="0"
|
||||
:recipe-id="mealplan.recipe ? mealplan.recipe.id : null"
|
||||
:image-height="125"
|
||||
class="mb-2"
|
||||
:route="mealplan.recipe ? true : false"
|
||||
|
|
7
frontend/run.sh
Normal file
7
frontend/run.sh
Normal file
|
@ -0,0 +1,7 @@
|
|||
# Production entry point for the frontend docker container
|
||||
|
||||
# Web Server
|
||||
caddy start --config /app/Caddyfile
|
||||
|
||||
# Start Node Application
|
||||
yarn start -p 3001
|
Loading…
Add table
Add a link
Reference in a new issue