Merge pull request 'work' (#1) from work into main

Reviewed-on: harmony/harmony-back#1
This commit is contained in:
2025-10-03 23:47:28 +02:00
49 changed files with 59774 additions and 28 deletions

80
config/cron-tasks.ts Normal file
View File

@@ -0,0 +1,80 @@
interface GNewsArticle {
title: string;
description: string;
url: string;
image: string;
publishedAt: string;
source: {
name: string;
url: string;
};
}
type GNewsResponse = {
articles: GNewsArticle[];
};
export default {
/**
* Simple example.
* Every monday at 1am.
*/
GNEWS: {
task: async ({ strapi }) => {
// Add your own logic here (e.g. send a queue of email, create a database backup, etc.).
const API_KEY = "b3760544f6efb233f19d32fe78cc1300";
const keywords = ["chorale"].join("+");
const url = `https://gnews.io/api/v4/search?q=${keywords}&lang=fr&max=10&token=${API_KEY}`;
try {
const response = await fetch(url);
const data = (await response.json()) as GNewsResponse;
const articles: GNewsArticle[] = data.articles;
for (const article of articles) {
const alreadyExists = await strapi.db
.query("api::post.post")
.findOne({
where: { url: article.url },
});
if (!alreadyExists) {
const post = await strapi.db.query("api::post.post").create({
data: {
title: article.title,
content: article.description,
url: article.url,
imageUrl: article.image,
source: article.source.name,
published_at: article.publishedAt,
//is_auto_generated: true,
category: "photo",
author: 14,
},
});
// Créer le postOwnership associé
await strapi.db.query("api::post-ownership.post-ownership").create({
data: {
post: post.id,
contextType: "system",
relation: "owner",
},
});
strapi.log.info(`🆕 Article créé : ${article.title}`);
} else {
strapi.log.info(`🔁 Article déjà existant : ${article.title}`);
}
}
} catch (error) {
strapi.log.error("❌ Erreur lors du fetch GNews :", error);
}
},
options: {
rule: "0 0 8 * * *",
},
},
};

View File

@@ -1,7 +1,13 @@
import cronTasks from "./cron-tasks";
export default ({ env }) => ({ export default ({ env }) => ({
host: env('HOST', '0.0.0.0'), host: env("HOST", "0.0.0.0"),
port: env.int('PORT', 1337), port: env.int("PORT", 1337),
app: { app: {
keys: env.array('APP_KEYS'), keys: env.array("APP_KEYS"),
},
cron: {
enabled: true,
tasks: cronTasks,
}, },
}); });

View File

@@ -0,0 +1,63 @@
{
"kind": "collectionType",
"collectionName": "ads",
"info": {
"singularName": "ad",
"pluralName": "ads",
"displayName": "Ad",
"description": ""
},
"options": {
"draftAndPublish": false
},
"pluginOptions": {},
"attributes": {
"title": {
"type": "string"
},
"category": {
"type": "enumeration",
"enum": [
"hire",
"equipment",
"management",
"sheetmusic",
"local"
]
},
"description": {
"type": "text"
},
"validUntil": {
"type": "date"
},
"author": {
"type": "relation",
"relation": "oneToOne",
"target": "plugin::users-permissions.user"
},
"views": {
"type": "integer"
},
"applies": {
"type": "integer"
},
"location": {
"type": "component",
"repeatable": false,
"component": "address.full-address"
},
"contactname": {
"type": "string"
},
"contactemail": {
"type": "string"
},
"contactphone": {
"type": "string"
},
"featured": {
"type": "boolean"
}
}
}

View File

@@ -0,0 +1,7 @@
/**
* ad controller
*/
import { factories } from '@strapi/strapi'
export default factories.createCoreController('api::ad.ad');

7
src/api/ad/routes/ad.ts Normal file
View File

@@ -0,0 +1,7 @@
/**
* ad router
*/
import { factories } from '@strapi/strapi';
export default factories.createCoreRouter('api::ad.ad');

View File

@@ -0,0 +1,7 @@
/**
* ad service
*/
import { factories } from '@strapi/strapi';
export default factories.createCoreService('api::ad.ad');

View File

@@ -0,0 +1,34 @@
{
"kind": "collectionType",
"collectionName": "comments",
"info": {
"singularName": "comment",
"pluralName": "comments",
"displayName": "Comment",
"description": ""
},
"options": {
"draftAndPublish": false
},
"pluginOptions": {},
"attributes": {
"content": {
"type": "string"
},
"likes": {
"type": "relation",
"relation": "oneToMany",
"target": "plugin::users-permissions.user"
},
"owner": {
"type": "relation",
"relation": "oneToOne",
"target": "plugin::users-permissions.user"
},
"replies": {
"type": "relation",
"relation": "oneToMany",
"target": "api::comment.comment"
}
}
}

View File

@@ -0,0 +1,7 @@
/**
* comment controller
*/
import { factories } from '@strapi/strapi'
export default factories.createCoreController('api::comment.comment');

View File

@@ -0,0 +1,7 @@
/**
* comment router
*/
import { factories } from '@strapi/strapi';
export default factories.createCoreRouter('api::comment.comment');

View File

@@ -0,0 +1,7 @@
/**
* comment service
*/
import { factories } from '@strapi/strapi';
export default factories.createCoreService('api::comment.comment');

View File

@@ -0,0 +1,33 @@
{
"kind": "collectionType",
"collectionName": "contact_metas",
"info": {
"singularName": "contact-meta",
"pluralName": "contact-metas",
"displayName": "ContactMeta"
},
"options": {
"draftAndPublish": false
},
"pluginOptions": {},
"attributes": {
"contact": {
"type": "relation",
"relation": "manyToOne",
"target": "api::contact.contact",
"inversedBy": "metas"
},
"owner": {
"type": "relation",
"relation": "manyToOne",
"target": "plugin::users-permissions.user",
"inversedBy": "contact_metas"
},
"favorite": {
"type": "boolean"
},
"note": {
"type": "string"
}
}
}

View File

@@ -0,0 +1,7 @@
/**
* contact-meta controller
*/
import { factories } from '@strapi/strapi'
export default factories.createCoreController('api::contact-meta.contact-meta');

View File

@@ -0,0 +1,7 @@
/**
* contact-meta router
*/
import { factories } from '@strapi/strapi';
export default factories.createCoreRouter('api::contact-meta.contact-meta');

View File

@@ -0,0 +1,7 @@
/**
* contact-meta service
*/
import { factories } from '@strapi/strapi';
export default factories.createCoreService('api::contact-meta.contact-meta');

View File

@@ -0,0 +1,53 @@
{
"kind": "collectionType",
"collectionName": "contacts",
"info": {
"singularName": "contact",
"pluralName": "contacts",
"displayName": "Contact",
"description": ""
},
"options": {
"draftAndPublish": false
},
"pluginOptions": {},
"attributes": {
"owner": {
"type": "relation",
"relation": "manyToOne",
"target": "plugin::users-permissions.user",
"inversedBy": "contacts"
},
"user": {
"type": "relation",
"relation": "manyToOne",
"target": "plugin::users-permissions.user",
"inversedBy": "related_contacts"
},
"state": {
"type": "enumeration",
"enum": [
"pending",
"accepted",
"rejected",
"blocked",
"follow"
]
},
"message": {
"type": "text"
},
"requestedAt": {
"type": "datetime"
},
"respondedAt": {
"type": "datetime"
},
"metas": {
"type": "relation",
"relation": "oneToMany",
"target": "api::contact-meta.contact-meta",
"mappedBy": "contact"
}
}
}

View File

@@ -0,0 +1,196 @@
/**
* contact controller
*/
import { factories } from "@strapi/strapi";
export default factories.createCoreController(
"api::contact.contact",
({ strapi }) => ({
async find(ctx) {
// Log de la requête entrante
console.log("GET /contacts - Requête reçue:", {
query: ctx.query,
params: ctx.params,
user: ctx.state.user?.id || "anonymous",
});
// Appel de la méthode parent
const result = await super.find(ctx);
// Log du résultat avant le return
console.log("GET /contacts - Résultat:", {
dataCount: result.data?.length || 0,
meta: result.meta,
});
return result;
},
async findOne(ctx) {
// Log de la requête entrante
console.log("GET /contacts/:id - Requête reçue:", {
id: ctx.params.id,
query: ctx.query,
user: ctx.state.user?.id || "anonymous",
});
// Appel de la méthode parent
const result = await super.findOne(ctx);
// Log du résultat avant le return
console.log("GET /contacts/:id - Résultat:", {
found: !!result.data,
id: result.data?.id,
});
return result;
},
/**
* Creates or updates a contact.
*
* If a contact already exists with the same owner and user,
* only the state field is updated. Otherwise, a new contact is created.
*
* @param {Object} ctx - The request context.
* @returns {Object} The created or updated contact.
*/
async create(ctx) {
// Log de la requête entrante
console.log("POST /contacts - Requête reçue:", {
body: ctx.request.body,
user: ctx.state.user?.id || "anonymous",
});
try {
const { data } = ctx.request.body;
const { owner, user, state } = data;
// Vérifier si un contact existe déjà avec le même owner et user
const existingContact = await strapi
.documents("api::contact.contact")
.findFirst({
filters: {
owner: owner,
user: user,
},
});
if (existingContact) {
// Mettre à jour uniquement le state
console.log(
"POST /contacts - Contact existant trouvé, mise à jour du state:",
{
documentId: existingContact.documentId,
oldState: existingContact.state,
newState: state,
}
);
const updatedContact = await strapi
.documents("api::contact.contact")
.update({
documentId: existingContact.documentId,
data: {
state: state,
respondedAt: new Date(),
},
});
return { data: updatedContact };
}
// Sinon, créer un nouveau contact
console.log("POST /contacts - Création d'un nouveau contact");
const result = await super.create(ctx);
console.log("POST /contacts - Contact créé:", {
id: result.data?.id,
});
return result;
} catch (error) {
// Log de l'erreur
console.error("POST /contacts - Erreur:", {
error: error.message,
user: ctx.state.user?.id || "anonymous",
});
throw error;
}
},
/**
* Deletes a contact by its ID.
*
* Logs the incoming request and attempts to find and delete the contact
* document. If the contact has associated meta documents owned by the user,
* those are deleted as well. If the contact is not found, a 404 response is returned.
* Logs any errors encountered during the process.
*
* @param {Object} ctx - The request context.
* @returns {Object} The response indicating the result of the delete operation.
*/
async delete(ctx) {
// Log de la requête entrante
console.log("DELETE /contacts/:id - Requête reçue:", {
id: ctx.params.id,
query: ctx.query,
user: ctx.state.user?.id || "anonymous",
});
const { id } = ctx.params;
try {
const result = await strapi
.documents("api::contact.contact")
.findFirst({
filters: {
$or: [
{
user: id,
},
{
owner: id,
},
],
},
populate: {
metas: {
populate: ["owner"],
},
},
});
if (!result) {
return ctx.notFound("Contact not found");
}
if (result.metas) {
const metaDocumentId = result.metas.filter(
(s) => s.owner.id === ctx.state.user?.id
)[0]?.documentId;
if (metaDocumentId) {
await strapi.documents("api::contact-meta.contact-meta").delete({
documentId: metaDocumentId,
});
}
}
await strapi
.documents("api::contact.contact")
.delete({ documentId: result.documentId });
ctx.send({ message: "Contact deleted successfully" }, 200);
} catch (error) {
// Log de l'erreur
console.error("DELETE /contacts/:id - Erreur:", {
id: ctx.params.id,
error: error.message,
user: ctx.state.user?.id || "anonymous",
});
throw error;
}
},
})
);

View File

@@ -0,0 +1,7 @@
/**
* contact router
*/
import { factories } from '@strapi/strapi';
export default factories.createCoreRouter('api::contact.contact');

View File

@@ -0,0 +1,7 @@
/**
* contact service
*/
import { factories } from '@strapi/strapi';
export default factories.createCoreService('api::contact.contact');

View File

@@ -0,0 +1,37 @@
{
"kind": "collectionType",
"collectionName": "group_memberships",
"info": {
"singularName": "group-membership",
"pluralName": "group-memberships",
"displayName": "GroupMembership",
"description": ""
},
"options": {
"draftAndPublish": false
},
"pluginOptions": {},
"attributes": {
"user": {
"type": "relation",
"relation": "manyToOne",
"target": "plugin::users-permissions.user",
"inversedBy": "group_memberships"
},
"group": {
"type": "relation",
"relation": "manyToOne",
"target": "api::group.group",
"inversedBy": "memberships"
},
"role": {
"type": "enumeration",
"enum": [
"member",
"admin",
"owner",
"follow"
]
}
}
}

View File

@@ -0,0 +1,7 @@
/**
* group-membership controller
*/
import { factories } from '@strapi/strapi'
export default factories.createCoreController('api::group-membership.group-membership');

View File

@@ -0,0 +1,7 @@
/**
* group-membership router
*/
import { factories } from '@strapi/strapi';
export default factories.createCoreRouter('api::group-membership.group-membership');

View File

@@ -0,0 +1,7 @@
/**
* group-membership service
*/
import { factories } from '@strapi/strapi';
export default factories.createCoreService('api::group-membership.group-membership');

View File

@@ -0,0 +1,76 @@
{
"kind": "collectionType",
"collectionName": "groups",
"info": {
"singularName": "group",
"pluralName": "groups",
"displayName": "Group",
"description": ""
},
"options": {
"draftAndPublish": false
},
"pluginOptions": {},
"attributes": {
"name": {
"type": "string"
},
"description": {
"type": "text"
},
"banner": {
"type": "media",
"multiple": false,
"required": false,
"allowedTypes": [
"images",
"files",
"videos",
"audios"
]
},
"tags": {
"displayName": "tags",
"type": "component",
"repeatable": true,
"component": "social.tags"
},
"category": {
"type": "enumeration",
"enum": [
"mixedChoir",
"womensChoir",
"mensChoir",
"childrensChoir"
]
},
"access": {
"type": "enumeration",
"enum": [
"private",
"public",
"closed"
]
},
"memberships": {
"type": "relation",
"relation": "oneToMany",
"target": "api::group-membership.group-membership",
"mappedBy": "group"
},
"lastActivity": {
"type": "datetime"
},
"activityType": {
"type": "string"
},
"state": {
"type": "enumeration",
"enum": [
"open",
"recruiting",
"audition"
]
}
}
}

View File

@@ -0,0 +1,84 @@
// /src/api/group/controllers/group.ts
import { factories } from "@strapi/strapi";
export default factories.createCoreController(
"api::group.group",
({ strapi }) => ({
async create(ctx) {
// 1) Prépare le payload + upload banner
const body = ctx.request.body as any;
const data =
typeof body?.data === "string"
? JSON.parse(body.data)
: body?.data || {};
const bannerInput = (ctx.request.files as any)?.banner;
if (bannerInput) {
const file = Array.isArray(bannerInput) ? bannerInput[0] : bannerInput;
const uploaded = await strapi
.plugin("upload")
.service("upload")
.upload({
data: {
fileInfo: {
alternativeText: data?.name || "banner",
caption: "banner",
name: file.originalFilename || "banner",
},
},
files: file,
});
if (uploaded?.[0]?.id) {
data.banner = uploaded[0].id;
}
}
// 2) Crée le group via core controller
ctx.request.body = { data };
const response = await super.create(ctx); // { data: { id, attributes }, meta: {} }
// 3) Tente de créer le GroupMembership (owner) pour le user courant
const membershipMeta: {
success: boolean;
membershipId?: number;
error?: string;
} = { success: false };
try {
const userId = ctx.state.user?.id;
const groupId = response?.data?.id;
if (!userId) {
membershipMeta.error = "Unauthenticated user";
} else if (!groupId) {
membershipMeta.error = "Missing group id";
} else {
const membership = await strapi.entityService.create(
"api::group-membership.group-membership",
{
data: {
user: userId,
group: groupId,
role: "owner",
},
}
);
membershipMeta.success = true;
membershipMeta.membershipId = membership?.id as number;
}
} catch (err: any) {
strapi.log.error(
"[group.create] Erreur création GroupMembership:",
err
);
membershipMeta.error = err?.message || "Unknown error";
}
// 4) Ajoute le résultat du membership dans la meta sans casser la création du group
response.meta = { ...(response.meta || {}), membership: membershipMeta };
return response;
},
})
);

View File

@@ -0,0 +1,7 @@
/**
* group router
*/
import { factories } from '@strapi/strapi';
export default factories.createCoreRouter('api::group.group');

View File

@@ -0,0 +1,7 @@
/**
* group service
*/
import { factories } from '@strapi/strapi';
export default factories.createCoreService('api::group.group');

View File

@@ -0,0 +1,45 @@
{
"kind": "collectionType",
"collectionName": "invites",
"info": {
"singularName": "invite",
"pluralName": "invites",
"displayName": "Invite",
"description": ""
},
"options": {
"draftAndPublish": false
},
"pluginOptions": {},
"attributes": {
"invitedUser": {
"type": "relation",
"relation": "oneToOne",
"target": "plugin::users-permissions.user"
},
"name": {
"type": "string"
},
"description": {
"type": "text"
},
"invitedBy": {
"type": "relation",
"relation": "oneToOne",
"target": "plugin::users-permissions.user"
},
"type": {
"type": "enumeration",
"enum": [
"group",
"choral"
]
},
"members": {
"type": "integer"
},
"invitedDate": {
"type": "datetime"
}
}
}

View File

@@ -0,0 +1,7 @@
/**
* invite controller
*/
import { factories } from '@strapi/strapi'
export default factories.createCoreController('api::invite.invite');

View File

@@ -0,0 +1,7 @@
/**
* invite router
*/
import { factories } from '@strapi/strapi';
export default factories.createCoreRouter('api::invite.invite');

View File

@@ -0,0 +1,7 @@
/**
* invite service
*/
import { factories } from '@strapi/strapi';
export default factories.createCoreService('api::invite.invite');

View File

@@ -0,0 +1,50 @@
{
"kind": "collectionType",
"collectionName": "post_ownerships",
"info": {
"singularName": "post-ownership",
"pluralName": "post-ownerships",
"displayName": "PostOwnership",
"description": ""
},
"options": {
"draftAndPublish": false
},
"pluginOptions": {},
"attributes": {
"author": {
"type": "relation",
"relation": "manyToOne",
"target": "plugin::users-permissions.user",
"inversedBy": "post_ownerships"
},
"contextType": {
"type": "enumeration",
"enum": [
"user",
"group",
"system"
]
},
"contextId": {
"type": "integer"
},
"relation": {
"type": "enumeration",
"enum": [
"owner",
"saved",
"hidden"
]
},
"metas": {
"type": "json"
},
"post": {
"type": "relation",
"relation": "manyToOne",
"target": "api::post.post",
"inversedBy": "post_ownerships"
}
}
}

View File

@@ -0,0 +1,7 @@
/**
* post-ownership controller
*/
import { factories } from '@strapi/strapi'
export default factories.createCoreController('api::post-ownership.post-ownership');

View File

@@ -0,0 +1,7 @@
/**
* post-ownership router
*/
import { factories } from '@strapi/strapi';
export default factories.createCoreRouter('api::post-ownership.post-ownership');

View File

@@ -0,0 +1,7 @@
/**
* post-ownership service
*/
import { factories } from '@strapi/strapi';
export default factories.createCoreService('api::post-ownership.post-ownership');

View File

@@ -0,0 +1,65 @@
{
"kind": "collectionType",
"collectionName": "posts",
"info": {
"singularName": "post",
"pluralName": "posts",
"displayName": "Post",
"description": ""
},
"options": {
"draftAndPublish": false
},
"pluginOptions": {},
"attributes": {
"content": {
"type": "text"
},
"media": {
"type": "media",
"multiple": true,
"required": false,
"allowedTypes": ["images", "files", "videos", "audios"]
},
"likes": {
"type": "relation",
"relation": "oneToMany",
"target": "plugin::users-permissions.user"
},
"category": {
"type": "enumeration",
"enum": ["photo", "video", "event"]
},
"comments": {
"type": "relation",
"relation": "oneToMany",
"target": "api::comment.comment"
},
"shares": {
"type": "integer"
},
"title": {
"type": "string"
},
"url": {
"type": "string"
},
"imageUrl": {
"type": "string"
},
"source": {
"type": "string"
},
"post_ownerships": {
"type": "relation",
"relation": "oneToMany",
"target": "api::post-ownership.post-ownership",
"mappedBy": "post"
},
"taggedContacts": {
"type": "relation",
"relation": "oneToMany",
"target": "plugin::users-permissions.user"
}
}
}

View File

@@ -0,0 +1,583 @@
/**
* post controller
*/
import { factories } from "@strapi/strapi";
export default factories.createCoreController(
"api::post.post",
({ strapi }) => ({
async create(ctx) {
const data = JSON.parse(ctx.request.body.data);
if (ctx.request.files.media) {
const files = Array.isArray(ctx.request.files.media)
? ctx.request.files.media[0]
: ctx.request.files.media;
const extension = files.originalFilename.match(/\.[0-9a-z]+$/i);
const payload = {
fileInfo: {
caption: "undefined",
alternativeText: data.name || "",
name: `${data.name}_media${extension}`,
},
};
const asset = await strapi.services["plugin::upload.upload"].upload({
data: payload,
files,
});
data.media = asset[0].id;
}
// Transformer taggedContacts en format de relation Strapi
if (data.taggedContacts && Array.isArray(data.taggedContacts)) {
data.taggedContacts = data.taggedContacts.map((id) => ({ id }));
}
delete data.author;
ctx.request.body = { data };
const result = await super.create(ctx);
// Créer le postOwnership associé
if (result.data) {
const userId = ctx.state.user?.id;
await strapi.db.query("api::post-ownership.post-ownership").create({
data: {
post: result.data.id,
author: userId,
contextType: "user",
contextId: userId,
relation: "owner",
},
});
}
return result;
},
/**
* Retrieves the feed for the authenticated user, including posts from friends, followed contacts,
* user's groups, friends' groups, and system posts. The feed is enriched with additional properties
* such as friend, contactFollow, member, and group.
*
* @param {Object} ctx - The Koa context object containing the request and response.
* @returns {Promise<void>} - Sends the enriched feed as the response.
*/
async feed(ctx) {
const userId = ctx.state.user?.id;
const { _limit = 20, _start = 0 } = ctx.query;
if (!userId) return ctx.badRequest("userId is required");
const queryParams = {
populate: {
post: {
populate: {
likes: true,
comments: {
populate: {
owner: {
populate: {
avatar: true,
},
},
},
},
author: {
populate: {
avatar: true,
},
},
media: true,
taggedContacts: {
populate: {
avatar: true,
},
},
},
},
author: {
populate: {
avatar: true,
},
},
// populate context info if needed
},
sort: { createdAt: "desc" },
pagination: {
start: parseInt(String(_start)),
limit: parseInt(String(_limit)),
},
};
// 1⃣ Récupérer les groupes de l'utilisateur
const groups = await strapi.db
.query("api::group-membership.group-membership")
.findMany({
where: { user: { id: parseInt(userId) } },
populate: ["group"],
});
const groupIds = groups.map((g) => g.group.id);
// 2⃣ Récupérer les amis (contacts acceptés) et les follows
const friendsContacts = await strapi.db
.query("api::contact.contact")
.findMany({
where: {
$or: [
{ owner: { id: parseInt(userId) } },
{ user: { id: parseInt(userId) } },
],
state: "accepted",
},
populate: ["owner", "user"],
});
const friendIds = friendsContacts.map((c) =>
c.owner.id !== parseInt(userId) ? c.owner.id : c.user.id
);
// Récupérer les contacts suivis (follow)
const followContacts = await strapi.db
.query("api::contact.contact")
.findMany({
where: {
owner: { id: parseInt(userId) },
state: "follow",
},
populate: ["user"],
});
const followIds = followContacts.map((c) => c.user.id);
// Récupérer les contacts suivis (follow)
const blockedContacts = await strapi.db
.query("api::contact.contact")
.findMany({
where: {
owner: { id: parseInt(userId) },
state: "blocked",
},
populate: ["user"],
});
const blockedIds = blockedContacts.map((c) => c.user.id);
// 3⃣ Récupérer les groupes où mes amis sont membres (en excluant mes propres groupes)
const friendsGroupMemberships = await strapi.db
.query("api::group-membership.group-membership")
.findMany({
where: { user: { id: friendIds } },
populate: ["group"],
});
const friendsGroupIds = friendsGroupMemberships
.map((membership) => membership.group.id)
.filter((groupId) => !groupIds.includes(groupId)); // Exclure mes propres groupes
// 4⃣ Récupérer le feed
const feed = await strapi.db
.query("api::post-ownership.post-ownership")
.findMany({
where: {
$or: [
// Posts réguliers (amis, follows, groupes, système, mes propres posts) - exclure les cachés
{
$and: [
{
$or: [
{ author: { id: friendIds } }, // Posts de mes amis
{ author: { id: followIds } }, // Posts des contacts que je suis
{ author: { id: parseInt(userId) }, relation: "owner" }, // Mes propres posts
{ contextType: "group", contextId: groupIds }, // Posts de mes groupes
{ contextType: "group", contextId: friendsGroupIds }, // Posts des groupes de mes amis
{ contextType: "system" }, // Posts système
],
},
{ relation: { $ne: "hidden" } }, // exclure les posts masqués pour les posts réguliers
],
},
// Posts sauvés par l'utilisateur
{
contextType: "user",
contextId: parseInt(userId),
relation: "saved",
},
// Posts cachés par l'utilisateur
{
contextType: "user",
contextId: parseInt(userId),
relation: "hidden",
},
],
},
...queryParams,
});
// 5⃣ Récupérer tous les groupes mentionnés dans le feed pour les populer
const allGroupIds = [...new Set([...groupIds, ...friendsGroupIds])];
const allGroups = await strapi.db.query("api::group.group").findMany({
where: { id: allGroupIds },
});
// Créer un map pour un accès rapide aux groupes par ID
const groupsMap = new Map(allGroups.map((group) => [group.id, group]));
// 6⃣ Enrichir le feed avec les propriétés friend, member, contactFollow et group
const enrichedFeed = feed.map((postOwnership) => {
const authorId = postOwnership.author?.id;
const contextType = postOwnership.contextType;
const contextId = postOwnership.contextId;
// Vérifier si l'auteur est un ami
const isFriend = authorId ? friendIds.includes(authorId) : false;
// Vérifier si l'auteur est un contact suivi
const isContactFollow = authorId ? followIds.includes(authorId) : false;
// Vérifier si l'auteur est bloqué
const isBlocked = authorId ? blockedIds.includes(authorId) : false;
// Vérifier si je suis membre du groupe (seulement pour les posts de groupe)
const isMember =
contextType === "group" && contextId
? groupIds.includes(contextId)
: false;
// Ajouter l'objet group si contextType est "group"
const group =
contextType === "group" && contextId
? groupsMap.get(contextId)
: null;
return {
...postOwnership,
friend: isFriend,
contactFollow: isContactFollow,
member: isMember,
blocked: isBlocked,
...(group && { group }), // Ajouter group seulement s'il existe
};
});
const filteredFeedBlocked = enrichedFeed.filter((post) => !post.blocked);
// Trier par createdAt du post (le plus récent en premier)
const sortedFeed = filteredFeedBlocked.sort((a, b) => {
const dateA = a.post?.createdAt
? new Date(a.post.createdAt).getTime()
: 0;
const dateB = b.post?.createdAt
? new Date(b.post.createdAt).getTime()
: 0;
return dateB - dateA; // Ordre décroissant (plus récent en premier)
});
ctx.send(sortedFeed);
},
async likePost(ctx) {
const user = ctx.state.user; // Utilisateur actuellement connecté
if (!user) {
return ctx.unauthorized("You must be logged in to like a post.");
}
const postId = parseInt(String(ctx.params.id));
if (isNaN(postId)) {
return ctx.badRequest("Invalid post ID");
}
// Verify the post exists
const existingPost = await strapi.db.query("api::post.post").findOne({
where: {
id: postId,
},
populate: {
likes: true,
},
});
if (!existingPost) {
return ctx.notFound("Post not found");
}
// Check if the user has already liked the post
const existingLike = existingPost.likes.find(
(like) => like.id === user.id
);
if (existingLike) {
await strapi.db.query("api::post.post").update({
where: { id: postId },
data: {
likes: {
disconnect: [{ id: user.id }],
},
},
});
return ctx.send({ message: "Post unliked successfully" });
}
// Update the postLikes field on the post entity
await strapi.db.query("api::post.post").update({
where: { id: postId },
data: {
likes: {
connect: [{ id: user.id }],
},
},
});
ctx.send({ message: "Post liked successfully" });
},
async savePost(ctx) {
console.log("🎯 savePost called with params:", ctx.params);
console.log("🎯 Request method:", ctx.request.method);
console.log("🎯 Request URL:", ctx.request.url);
const user = ctx.state.user;
if (!user) {
console.log("❌ User not authenticated");
return ctx.unauthorized("You must be logged in to save posts");
}
console.log("✅ User authenticated:", user.id);
const postId = parseInt(String(ctx.params.id));
const authorId = parseInt(String(ctx.query.authorId));
if (isNaN(postId)) {
return ctx.badRequest("Invalid post ID");
}
// Verify the post exists
const post = await strapi.entityService.findOne("api::post.post", postId);
if (!post) {
return ctx.notFound("Post not found");
}
// Check if post is already saved by this user
const existingSavedPost = await strapi.db
.query("api::post-ownership.post-ownership")
.findOne({
where: {
post: postId,
author: authorId,
contextType: "user",
contextId: user.id,
relation: "saved",
},
});
if (existingSavedPost) {
console.log("⚠️ Post already saved");
return ctx.badRequest("Post is already saved");
}
// Create postOwnership entry to mark post as saved
const savedPostOwnership = await strapi.db
.query("api::post-ownership.post-ownership")
.create({
data: {
post: postId,
author: authorId,
contextType: "user",
contextId: user.id,
relation: "saved",
},
});
console.log("✅ Post marked as saved:", savedPostOwnership);
// Get count of saved posts for this user
const savedPostsCount = await strapi.db
.query("api::post-ownership.post-ownership")
.count({
where: {
author: authorId,
contextType: "user",
contextId: user.id,
relation: "saved",
},
});
return ctx.send({
message: "Post saved successfully",
savedPostsCount: savedPostsCount,
});
},
async removeSavedPost(ctx) {
console.log("🎯 removeSavedPost called with params:", ctx.params);
console.log("🎯 Request method:", ctx.request.method);
console.log("🎯 Request URL:", ctx.request.url);
const user = ctx.state.user;
if (!user) {
console.log("❌ User not authenticated");
return ctx.unauthorized("You must be logged in to remove saved posts");
}
console.log("✅ User authenticated:", user.id);
const postId = parseInt(String(ctx.params.id));
if (isNaN(postId)) {
return ctx.badRequest("Invalid post ID");
}
// Verify the post exists
const post = await strapi.entityService.findOne("api::post.post", postId);
if (!post) {
return ctx.notFound("Post not found");
}
// Find the saved post ownership entry
const savedPostOwnership = await strapi.db
.query("api::post-ownership.post-ownership")
.findOne({
where: {
post: postId,
contextType: "user",
contextId: user.id,
relation: "saved",
},
});
if (!savedPostOwnership) {
console.log("⚠️ Post is not saved");
return ctx.badRequest("Post is not in your saved posts");
}
// Delete the saved post ownership entry
await strapi.db.query("api::post-ownership.post-ownership").delete({
where: { id: savedPostOwnership.id },
});
console.log("✅ Post removed from saved posts successfully");
// Get updated count of saved posts for this user
const savedPostsCount = await strapi.db
.query("api::post-ownership.post-ownership")
.count({
where: {
author: user.id,
contextType: "user",
contextId: user.id,
relation: "saved",
},
});
return ctx.send({
message: "Post removed from saved posts successfully",
savedPostsCount: savedPostsCount,
});
},
async hidePost(ctx) {
console.log("🎯 hidePost called with params:", ctx.params);
console.log("🎯 Request method:", ctx.request.method);
console.log("🎯 Request URL:", ctx.request.url);
const user = ctx.state.user;
if (!user) {
console.log("❌ User not authenticated");
return ctx.unauthorized("You must be logged in to hide posts");
}
console.log("✅ User authenticated:", user.id);
const postId = parseInt(String(ctx.params.id));
if (isNaN(postId)) {
return ctx.badRequest("Invalid post ID");
}
// Verify the post exists
const post = await strapi.entityService.findOne("api::post.post", postId);
if (!post) {
return ctx.notFound("Post not found");
}
// Check if post is already hidden by this user
const existingHiddenPost = await strapi.db
.query("api::post-ownership.post-ownership")
.findOne({
where: {
post: postId,
author: user.id,
contextType: "user",
contextId: user.id,
relation: "hidden",
},
});
if (existingHiddenPost) {
console.log("⚠️ Post already hidden");
return ctx.badRequest("Post is already hidden");
}
// Create postOwnership entry to mark post as hidden
const hiddenPostOwnership = await strapi.db
.query("api::post-ownership.post-ownership")
.create({
data: {
post: postId,
author: user.id,
contextType: "user",
contextId: user.id,
relation: "hidden",
},
});
console.log("✅ Post marked as hidden:", hiddenPostOwnership);
return ctx.send({
message: "Post hidden successfully",
hiddenPostId: postId,
});
},
async removeHiddenPost(ctx) {
console.log("🎯 removeHiddenPost called with params:", ctx.params);
console.log("🎯 Request method:", ctx.request.method);
console.log("🎯 Request URL:", ctx.request.url);
const user = ctx.state.user;
if (!user) {
console.log("❌ User not authenticated");
return ctx.unauthorized("You must be logged in to unhide posts");
}
console.log("✅ User authenticated:", user.id);
const postId = parseInt(String(ctx.params.id));
if (isNaN(postId)) {
return ctx.badRequest("Invalid post ID");
}
// Verify the post exists
const post = await strapi.entityService.findOne("api::post.post", postId);
if (!post) {
return ctx.notFound("Post not found");
}
// Find the hidden post ownership entry
const hiddenPostOwnership = await strapi.db
.query("api::post-ownership.post-ownership")
.findOne({
where: {
post: postId,
author: user.id,
contextType: "user",
contextId: user.id,
relation: "hidden",
},
});
if (!hiddenPostOwnership) {
console.log("⚠️ Post is not hidden");
return ctx.badRequest("Post is not hidden");
}
// Delete the hidden post ownership entry
await strapi.db.query("api::post-ownership.post-ownership").delete({
where: { id: hiddenPostOwnership.id },
});
console.log("✅ Post unhidden successfully");
return ctx.send({
message: "Post unhidden successfully",
unhiddenPostId: postId,
});
},
})
);

View File

@@ -0,0 +1,38 @@
/**
* Custom post routes
*/
export default {
routes: [
{
method: "POST",
path: "/posts/:id/save",
handler: "post.savePost",
},
{
method: "DELETE",
path: "/posts/:id/save",
handler: "post.removeSavedPost",
},
{
method: "POST",
path: "/posts/:id/hide",
handler: "post.hidePost",
},
{
method: "DELETE",
path: "/posts/:id/hide",
handler: "post.removeHiddenPost",
},
{
method: "GET",
path: "/posts/feed",
handler: "post.feed",
},
{
method: "PUT",
path: "/posts/:id/like",
handler: "post.likePost",
},
],
};

View File

@@ -0,0 +1,7 @@
/**
* post router
*/
import { factories } from "@strapi/strapi";
export default factories.createCoreRouter("api::post.post");

View File

@@ -0,0 +1,7 @@
/**
* post service
*/
import { factories } from '@strapi/strapi';
export default factories.createCoreService('api::post.post');

View File

@@ -0,0 +1,34 @@
{
"kind": "collectionType",
"collectionName": "reports",
"info": {
"singularName": "report",
"pluralName": "reports",
"displayName": "report",
"description": ""
},
"options": {
"draftAndPublish": false
},
"pluginOptions": {},
"attributes": {
"details": {
"type": "text"
},
"reason": {
"type": "enumeration",
"enum": [
"inappropriate",
"fraudulent",
"duplicate",
"expired",
"other"
]
},
"reporter": {
"type": "relation",
"relation": "oneToOne",
"target": "plugin::users-permissions.user"
}
}
}

View File

@@ -0,0 +1,7 @@
/**
* report controller
*/
import { factories } from '@strapi/strapi'
export default factories.createCoreController('api::report.report');

View File

@@ -0,0 +1,7 @@
/**
* report router
*/
import { factories } from '@strapi/strapi';
export default factories.createCoreRouter('api::report.report');

View File

@@ -0,0 +1,7 @@
/**
* report service
*/
import { factories } from '@strapi/strapi';
export default factories.createCoreService('api::report.report');

View File

@@ -0,0 +1,35 @@
{
"collectionName": "components_address_full_addresses",
"info": {
"displayName": "FullAddress",
"icon": "pinMap",
"description": ""
},
"options": {},
"attributes": {
"freeaddress": {
"type": "string"
},
"housenumber": {
"type": "string"
},
"road": {
"type": "string"
},
"city": {
"type": "string"
},
"country": {
"type": "string"
},
"postcode": {
"type": "string"
},
"long": {
"type": "decimal"
},
"lat": {
"type": "decimal"
}
}
}

View File

@@ -0,0 +1,13 @@
{
"collectionName": "components_social_tags",
"info": {
"displayName": "tags",
"icon": "emotionUnhappy"
},
"options": {},
"attributes": {
"tag": {
"type": "string"
}
}
}

View File

@@ -181,6 +181,49 @@
"relation": "oneToMany", "relation": "oneToMany",
"target": "api::announcement.announcement", "target": "api::announcement.announcement",
"mappedBy": "author" "mappedBy": "author"
},
"friends": {
"type": "relation",
"relation": "oneToMany",
"target": "plugin::users-permissions.user"
},
"group_memberships": {
"type": "relation",
"relation": "oneToMany",
"target": "api::group-membership.group-membership",
"mappedBy": "user"
},
"saved_posts": {
"type": "relation",
"relation": "oneToMany",
"target": "api::post.post"
},
"contacts": {
"type": "relation",
"relation": "oneToMany",
"target": "api::contact.contact",
"mappedBy": "owner"
},
"related_contacts": {
"type": "relation",
"relation": "oneToMany",
"target": "api::contact.contact",
"mappedBy": "user"
},
"contact_metas": {
"type": "relation",
"relation": "oneToMany",
"target": "api::contact-meta.contact-meta",
"mappedBy": "owner"
},
"phone": {
"type": "string"
},
"post_ownerships": {
"type": "relation",
"relation": "oneToMany",
"target": "api::post-ownership.post-ownership",
"mappedBy": "author"
} }
} }
} }

View File

@@ -1,5 +1,35 @@
import type { Schema, Struct } from '@strapi/strapi'; import type { Schema, Struct } from '@strapi/strapi';
export interface AddressFullAddress extends Struct.ComponentSchema {
collectionName: 'components_address_full_addresses';
info: {
description: '';
displayName: 'FullAddress';
icon: 'pinMap';
};
attributes: {
city: Schema.Attribute.String;
country: Schema.Attribute.String;
freeaddress: Schema.Attribute.String;
housenumber: Schema.Attribute.String;
lat: Schema.Attribute.Decimal;
long: Schema.Attribute.Decimal;
postcode: Schema.Attribute.String;
road: Schema.Attribute.String;
};
}
export interface SocialTags extends Struct.ComponentSchema {
collectionName: 'components_social_tags';
info: {
displayName: 'tags';
icon: 'emotionUnhappy';
};
attributes: {
tag: Schema.Attribute.String;
};
}
export interface UserPermissions extends Struct.ComponentSchema { export interface UserPermissions extends Struct.ComponentSchema {
collectionName: 'components_user_permissions'; collectionName: 'components_user_permissions';
info: { info: {
@@ -19,6 +49,8 @@ export interface UserPermissions extends Struct.ComponentSchema {
declare module '@strapi/strapi' { declare module '@strapi/strapi' {
export module Public { export module Public {
export interface ComponentSchemas { export interface ComponentSchemas {
'address.full-address': AddressFullAddress;
'social.tags': SocialTags;
'user.permissions': UserPermissions; 'user.permissions': UserPermissions;
} }
} }

View File

@@ -369,6 +369,48 @@ export interface AdminUser extends Struct.CollectionTypeSchema {
}; };
} }
export interface ApiAdAd extends Struct.CollectionTypeSchema {
collectionName: 'ads';
info: {
description: '';
displayName: 'Ad';
pluralName: 'ads';
singularName: 'ad';
};
options: {
draftAndPublish: false;
};
attributes: {
applies: Schema.Attribute.Integer;
author: Schema.Attribute.Relation<
'oneToOne',
'plugin::users-permissions.user'
>;
category: Schema.Attribute.Enumeration<
['hire', 'equipment', 'management', 'sheetmusic', 'local']
>;
contactemail: Schema.Attribute.String;
contactname: Schema.Attribute.String;
contactphone: Schema.Attribute.String;
createdAt: Schema.Attribute.DateTime;
createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
Schema.Attribute.Private;
description: Schema.Attribute.Text;
featured: Schema.Attribute.Boolean;
locale: Schema.Attribute.String & Schema.Attribute.Private;
localizations: Schema.Attribute.Relation<'oneToMany', 'api::ad.ad'> &
Schema.Attribute.Private;
location: Schema.Attribute.Component<'address.full-address', false>;
publishedAt: Schema.Attribute.DateTime;
title: Schema.Attribute.String;
updatedAt: Schema.Attribute.DateTime;
updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
Schema.Attribute.Private;
validUntil: Schema.Attribute.Date;
views: Schema.Attribute.Integer;
};
}
export interface ApiAnnouncementAnnouncement export interface ApiAnnouncementAnnouncement
extends Struct.CollectionTypeSchema { extends Struct.CollectionTypeSchema {
collectionName: 'announcements'; collectionName: 'announcements';
@@ -678,6 +720,124 @@ export interface ApiChoralChoral extends Struct.CollectionTypeSchema {
}; };
} }
export interface ApiCommentComment extends Struct.CollectionTypeSchema {
collectionName: 'comments';
info: {
description: '';
displayName: 'Comment';
pluralName: 'comments';
singularName: 'comment';
};
options: {
draftAndPublish: false;
};
attributes: {
content: Schema.Attribute.String;
createdAt: Schema.Attribute.DateTime;
createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
Schema.Attribute.Private;
likes: Schema.Attribute.Relation<
'oneToMany',
'plugin::users-permissions.user'
>;
locale: Schema.Attribute.String & Schema.Attribute.Private;
localizations: Schema.Attribute.Relation<
'oneToMany',
'api::comment.comment'
> &
Schema.Attribute.Private;
owner: Schema.Attribute.Relation<
'oneToOne',
'plugin::users-permissions.user'
>;
publishedAt: Schema.Attribute.DateTime;
replies: Schema.Attribute.Relation<'oneToMany', 'api::comment.comment'>;
updatedAt: Schema.Attribute.DateTime;
updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
Schema.Attribute.Private;
};
}
export interface ApiContactMetaContactMeta extends Struct.CollectionTypeSchema {
collectionName: 'contact_metas';
info: {
displayName: 'ContactMeta';
pluralName: 'contact-metas';
singularName: 'contact-meta';
};
options: {
draftAndPublish: false;
};
attributes: {
contact: Schema.Attribute.Relation<'manyToOne', 'api::contact.contact'>;
createdAt: Schema.Attribute.DateTime;
createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
Schema.Attribute.Private;
favorite: Schema.Attribute.Boolean;
locale: Schema.Attribute.String & Schema.Attribute.Private;
localizations: Schema.Attribute.Relation<
'oneToMany',
'api::contact-meta.contact-meta'
> &
Schema.Attribute.Private;
note: Schema.Attribute.String;
owner: Schema.Attribute.Relation<
'manyToOne',
'plugin::users-permissions.user'
>;
publishedAt: Schema.Attribute.DateTime;
updatedAt: Schema.Attribute.DateTime;
updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
Schema.Attribute.Private;
};
}
export interface ApiContactContact extends Struct.CollectionTypeSchema {
collectionName: 'contacts';
info: {
description: '';
displayName: 'Contact';
pluralName: 'contacts';
singularName: 'contact';
};
options: {
draftAndPublish: false;
};
attributes: {
createdAt: Schema.Attribute.DateTime;
createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
Schema.Attribute.Private;
locale: Schema.Attribute.String & Schema.Attribute.Private;
localizations: Schema.Attribute.Relation<
'oneToMany',
'api::contact.contact'
> &
Schema.Attribute.Private;
message: Schema.Attribute.Text;
metas: Schema.Attribute.Relation<
'oneToMany',
'api::contact-meta.contact-meta'
>;
owner: Schema.Attribute.Relation<
'manyToOne',
'plugin::users-permissions.user'
>;
publishedAt: Schema.Attribute.DateTime;
requestedAt: Schema.Attribute.DateTime;
respondedAt: Schema.Attribute.DateTime;
state: Schema.Attribute.Enumeration<
['pending', 'accepted', 'rejected', 'blocked', 'follow']
>;
updatedAt: Schema.Attribute.DateTime;
updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
Schema.Attribute.Private;
user: Schema.Attribute.Relation<
'manyToOne',
'plugin::users-permissions.user'
>;
};
}
export interface ApiConversationConversation export interface ApiConversationConversation
extends Struct.CollectionTypeSchema { extends Struct.CollectionTypeSchema {
collectionName: 'conversations'; collectionName: 'conversations';
@@ -835,6 +995,122 @@ export interface ApiEventEvent extends Struct.CollectionTypeSchema {
}; };
} }
export interface ApiGroupMembershipGroupMembership
extends Struct.CollectionTypeSchema {
collectionName: 'group_memberships';
info: {
description: '';
displayName: 'GroupMembership';
pluralName: 'group-memberships';
singularName: 'group-membership';
};
options: {
draftAndPublish: false;
};
attributes: {
createdAt: Schema.Attribute.DateTime;
createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
Schema.Attribute.Private;
group: Schema.Attribute.Relation<'manyToOne', 'api::group.group'>;
locale: Schema.Attribute.String & Schema.Attribute.Private;
localizations: Schema.Attribute.Relation<
'oneToMany',
'api::group-membership.group-membership'
> &
Schema.Attribute.Private;
publishedAt: Schema.Attribute.DateTime;
role: Schema.Attribute.Enumeration<['member', 'admin', 'owner', 'follow']>;
updatedAt: Schema.Attribute.DateTime;
updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
Schema.Attribute.Private;
user: Schema.Attribute.Relation<
'manyToOne',
'plugin::users-permissions.user'
>;
};
}
export interface ApiGroupGroup extends Struct.CollectionTypeSchema {
collectionName: 'groups';
info: {
description: '';
displayName: 'Group';
pluralName: 'groups';
singularName: 'group';
};
options: {
draftAndPublish: false;
};
attributes: {
access: Schema.Attribute.Enumeration<['private', 'public', 'closed']>;
activityType: Schema.Attribute.String;
banner: Schema.Attribute.Media<'images' | 'files' | 'videos' | 'audios'>;
category: Schema.Attribute.Enumeration<
['mixedChoir', 'womensChoir', 'mensChoir', 'childrensChoir']
>;
createdAt: Schema.Attribute.DateTime;
createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
Schema.Attribute.Private;
description: Schema.Attribute.Text;
lastActivity: Schema.Attribute.DateTime;
locale: Schema.Attribute.String & Schema.Attribute.Private;
localizations: Schema.Attribute.Relation<'oneToMany', 'api::group.group'> &
Schema.Attribute.Private;
memberships: Schema.Attribute.Relation<
'oneToMany',
'api::group-membership.group-membership'
>;
name: Schema.Attribute.String;
publishedAt: Schema.Attribute.DateTime;
state: Schema.Attribute.Enumeration<['open', 'recruiting', 'audition']>;
tags: Schema.Attribute.Component<'social.tags', true>;
updatedAt: Schema.Attribute.DateTime;
updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
Schema.Attribute.Private;
};
}
export interface ApiInviteInvite extends Struct.CollectionTypeSchema {
collectionName: 'invites';
info: {
description: '';
displayName: 'Invite';
pluralName: 'invites';
singularName: 'invite';
};
options: {
draftAndPublish: false;
};
attributes: {
createdAt: Schema.Attribute.DateTime;
createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
Schema.Attribute.Private;
description: Schema.Attribute.Text;
invitedBy: Schema.Attribute.Relation<
'oneToOne',
'plugin::users-permissions.user'
>;
invitedDate: Schema.Attribute.DateTime;
invitedUser: Schema.Attribute.Relation<
'oneToOne',
'plugin::users-permissions.user'
>;
locale: Schema.Attribute.String & Schema.Attribute.Private;
localizations: Schema.Attribute.Relation<
'oneToMany',
'api::invite.invite'
> &
Schema.Attribute.Private;
members: Schema.Attribute.Integer;
name: Schema.Attribute.String;
publishedAt: Schema.Attribute.DateTime;
type: Schema.Attribute.Enumeration<['group', 'choral']>;
updatedAt: Schema.Attribute.DateTime;
updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
Schema.Attribute.Private;
};
}
export interface ApiMessageMessage extends Struct.CollectionTypeSchema { export interface ApiMessageMessage extends Struct.CollectionTypeSchema {
collectionName: 'messages'; collectionName: 'messages';
info: { info: {
@@ -904,6 +1180,129 @@ export interface ApiPermissionsTemplatePermissionsTemplate
}; };
} }
export interface ApiPostOwnershipPostOwnership
extends Struct.CollectionTypeSchema {
collectionName: 'post_ownerships';
info: {
description: '';
displayName: 'PostOwnership';
pluralName: 'post-ownerships';
singularName: 'post-ownership';
};
options: {
draftAndPublish: false;
};
attributes: {
author: Schema.Attribute.Relation<
'manyToOne',
'plugin::users-permissions.user'
>;
contextId: Schema.Attribute.Integer;
contextType: Schema.Attribute.Enumeration<['user', 'group', 'system']>;
createdAt: Schema.Attribute.DateTime;
createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
Schema.Attribute.Private;
locale: Schema.Attribute.String & Schema.Attribute.Private;
localizations: Schema.Attribute.Relation<
'oneToMany',
'api::post-ownership.post-ownership'
> &
Schema.Attribute.Private;
metas: Schema.Attribute.JSON;
post: Schema.Attribute.Relation<'manyToOne', 'api::post.post'>;
publishedAt: Schema.Attribute.DateTime;
relation: Schema.Attribute.Enumeration<['owner', 'saved', 'hidden']>;
updatedAt: Schema.Attribute.DateTime;
updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
Schema.Attribute.Private;
};
}
export interface ApiPostPost extends Struct.CollectionTypeSchema {
collectionName: 'posts';
info: {
description: '';
displayName: 'Post';
pluralName: 'posts';
singularName: 'post';
};
options: {
draftAndPublish: false;
};
attributes: {
category: Schema.Attribute.Enumeration<['photo', 'video', 'event']>;
comments: Schema.Attribute.Relation<'oneToMany', 'api::comment.comment'>;
content: Schema.Attribute.Text;
createdAt: Schema.Attribute.DateTime;
createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
Schema.Attribute.Private;
imageUrl: Schema.Attribute.String;
likes: Schema.Attribute.Relation<
'oneToMany',
'plugin::users-permissions.user'
>;
locale: Schema.Attribute.String & Schema.Attribute.Private;
localizations: Schema.Attribute.Relation<'oneToMany', 'api::post.post'> &
Schema.Attribute.Private;
media: Schema.Attribute.Media<
'images' | 'files' | 'videos' | 'audios',
true
>;
post_ownerships: Schema.Attribute.Relation<
'oneToMany',
'api::post-ownership.post-ownership'
>;
publishedAt: Schema.Attribute.DateTime;
shares: Schema.Attribute.Integer;
source: Schema.Attribute.String;
taggedContacts: Schema.Attribute.Relation<
'oneToMany',
'plugin::users-permissions.user'
>;
title: Schema.Attribute.String;
updatedAt: Schema.Attribute.DateTime;
updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
Schema.Attribute.Private;
url: Schema.Attribute.String;
};
}
export interface ApiReportReport extends Struct.CollectionTypeSchema {
collectionName: 'reports';
info: {
description: '';
displayName: 'report';
pluralName: 'reports';
singularName: 'report';
};
options: {
draftAndPublish: false;
};
attributes: {
createdAt: Schema.Attribute.DateTime;
createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
Schema.Attribute.Private;
details: Schema.Attribute.Text;
locale: Schema.Attribute.String & Schema.Attribute.Private;
localizations: Schema.Attribute.Relation<
'oneToMany',
'api::report.report'
> &
Schema.Attribute.Private;
publishedAt: Schema.Attribute.DateTime;
reason: Schema.Attribute.Enumeration<
['inappropriate', 'fraudulent', 'duplicate', 'expired', 'other']
>;
reporter: Schema.Attribute.Relation<
'oneToOne',
'plugin::users-permissions.user'
>;
updatedAt: Schema.Attribute.DateTime;
updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
Schema.Attribute.Private;
};
}
export interface PluginContentReleasesRelease export interface PluginContentReleasesRelease
extends Struct.CollectionTypeSchema { extends Struct.CollectionTypeSchema {
collectionName: 'strapi_releases'; collectionName: 'strapi_releases';
@@ -1385,6 +1784,11 @@ export interface PluginUsersPermissionsUser
chorals: Schema.Attribute.Relation<'manyToMany', 'api::choral.choral'>; chorals: Schema.Attribute.Relation<'manyToMany', 'api::choral.choral'>;
confirmationToken: Schema.Attribute.String & Schema.Attribute.Private; confirmationToken: Schema.Attribute.String & Schema.Attribute.Private;
confirmed: Schema.Attribute.Boolean & Schema.Attribute.DefaultTo<false>; confirmed: Schema.Attribute.Boolean & Schema.Attribute.DefaultTo<false>;
contact_metas: Schema.Attribute.Relation<
'oneToMany',
'api::contact-meta.contact-meta'
>;
contacts: Schema.Attribute.Relation<'oneToMany', 'api::contact.contact'>;
createdAt: Schema.Attribute.DateTime; createdAt: Schema.Attribute.DateTime;
createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> & createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
Schema.Attribute.Private; Schema.Attribute.Private;
@@ -1394,7 +1798,15 @@ export interface PluginUsersPermissionsUser
Schema.Attribute.SetMinMaxLength<{ Schema.Attribute.SetMinMaxLength<{
minLength: 6; minLength: 6;
}>; }>;
friends: Schema.Attribute.Relation<
'oneToMany',
'plugin::users-permissions.user'
>;
gender: Schema.Attribute.Enumeration<['men', 'women', 'none']>; gender: Schema.Attribute.Enumeration<['men', 'women', 'none']>;
group_memberships: Schema.Attribute.Relation<
'oneToMany',
'api::group-membership.group-membership'
>;
job: Schema.Attribute.Enumeration< job: Schema.Attribute.Enumeration<
['choir_director', 'choir_addict', 'choir_master', 'choir_singer', 'none'] ['choir_director', 'choir_addict', 'choir_master', 'choir_singer', 'none']
>; >;
@@ -1414,13 +1826,23 @@ export interface PluginUsersPermissionsUser
Schema.Attribute.SetMinMaxLength<{ Schema.Attribute.SetMinMaxLength<{
minLength: 6; minLength: 6;
}>; }>;
phone: Schema.Attribute.String;
post_ownerships: Schema.Attribute.Relation<
'oneToMany',
'api::post-ownership.post-ownership'
>;
provider: Schema.Attribute.String; provider: Schema.Attribute.String;
publishedAt: Schema.Attribute.DateTime; publishedAt: Schema.Attribute.DateTime;
related_contacts: Schema.Attribute.Relation<
'oneToMany',
'api::contact.contact'
>;
resetPasswordToken: Schema.Attribute.String & Schema.Attribute.Private; resetPasswordToken: Schema.Attribute.String & Schema.Attribute.Private;
role: Schema.Attribute.Relation< role: Schema.Attribute.Relation<
'manyToOne', 'manyToOne',
'plugin::users-permissions.role' 'plugin::users-permissions.role'
>; >;
saved_posts: Schema.Attribute.Relation<'oneToMany', 'api::post.post'>;
surname: Schema.Attribute.String; surname: Schema.Attribute.String;
updatedAt: Schema.Attribute.DateTime; updatedAt: Schema.Attribute.DateTime;
updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> & updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
@@ -1447,6 +1869,7 @@ declare module '@strapi/strapi' {
'admin::transfer-token': AdminTransferToken; 'admin::transfer-token': AdminTransferToken;
'admin::transfer-token-permission': AdminTransferTokenPermission; 'admin::transfer-token-permission': AdminTransferTokenPermission;
'admin::user': AdminUser; 'admin::user': AdminUser;
'api::ad.ad': ApiAdAd;
'api::announcement.announcement': ApiAnnouncementAnnouncement; 'api::announcement.announcement': ApiAnnouncementAnnouncement;
'api::board-card.board-card': ApiBoardCardBoardCard; 'api::board-card.board-card': ApiBoardCardBoardCard;
'api::board-list.board-list': ApiBoardListBoardList; 'api::board-list.board-list': ApiBoardListBoardList;
@@ -1455,12 +1878,21 @@ declare module '@strapi/strapi' {
'api::choral-membership.choral-membership': ApiChoralMembershipChoralMembership; 'api::choral-membership.choral-membership': ApiChoralMembershipChoralMembership;
'api::choral-permission.choral-permission': ApiChoralPermissionChoralPermission; 'api::choral-permission.choral-permission': ApiChoralPermissionChoralPermission;
'api::choral.choral': ApiChoralChoral; 'api::choral.choral': ApiChoralChoral;
'api::comment.comment': ApiCommentComment;
'api::contact-meta.contact-meta': ApiContactMetaContactMeta;
'api::contact.contact': ApiContactContact;
'api::conversation.conversation': ApiConversationConversation; 'api::conversation.conversation': ApiConversationConversation;
'api::direct-message.direct-message': ApiDirectMessageDirectMessage; 'api::direct-message.direct-message': ApiDirectMessageDirectMessage;
'api::event-other.event-other': ApiEventOtherEventOther; 'api::event-other.event-other': ApiEventOtherEventOther;
'api::event.event': ApiEventEvent; 'api::event.event': ApiEventEvent;
'api::group-membership.group-membership': ApiGroupMembershipGroupMembership;
'api::group.group': ApiGroupGroup;
'api::invite.invite': ApiInviteInvite;
'api::message.message': ApiMessageMessage; 'api::message.message': ApiMessageMessage;
'api::permissions-template.permissions-template': ApiPermissionsTemplatePermissionsTemplate; 'api::permissions-template.permissions-template': ApiPermissionsTemplatePermissionsTemplate;
'api::post-ownership.post-ownership': ApiPostOwnershipPostOwnership;
'api::post.post': ApiPostPost;
'api::report.report': ApiReportReport;
'plugin::content-releases.release': PluginContentReleasesRelease; 'plugin::content-releases.release': PluginContentReleasesRelease;
'plugin::content-releases.release-action': PluginContentReleasesReleaseAction; 'plugin::content-releases.release-action': PluginContentReleasesReleaseAction;
'plugin::i18n.locale': PluginI18NLocale; 'plugin::i18n.locale': PluginI18NLocale;