Implement cross-club friendly match concept with invitations and shared matches
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 49s
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 49s
- Added controllers for handling friendly match invitations and shared matches. - Created migration scripts for `friendly_match_invitation` and `friendly_match_shared` tables. - Developed models for `FriendlyMatchInvitation` and `FriendlyMatchShared`. - Established routes for managing invitations and shared matches. - Implemented services for business logic related to invitations and shared matches. - Documented the concept plan for the new feature including API endpoints and data models.
This commit is contained in:
96
backend/models/FriendlyMatchInvitation.js
Normal file
96
backend/models/FriendlyMatchInvitation.js
Normal file
@@ -0,0 +1,96 @@
|
||||
import { DataTypes } from 'sequelize';
|
||||
import sequelize from '../database.js';
|
||||
import Club from './Club.js';
|
||||
import User from './User.js';
|
||||
|
||||
const FriendlyMatchInvitation = sequelize.define('FriendlyMatchInvitation', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
allowNull: false,
|
||||
},
|
||||
fromClubId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: Club,
|
||||
key: 'id',
|
||||
},
|
||||
onDelete: 'CASCADE',
|
||||
field: 'from_club_id',
|
||||
},
|
||||
toClubId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: Club,
|
||||
key: 'id',
|
||||
},
|
||||
onDelete: 'CASCADE',
|
||||
field: 'to_club_id',
|
||||
},
|
||||
proposedDate: {
|
||||
type: DataTypes.DATEONLY,
|
||||
allowNull: false,
|
||||
field: 'proposed_date',
|
||||
},
|
||||
proposedStartTime: {
|
||||
type: DataTypes.TIME,
|
||||
allowNull: true,
|
||||
field: 'proposed_start_time',
|
||||
},
|
||||
proposedMatchName: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: false,
|
||||
field: 'proposed_match_name',
|
||||
},
|
||||
message: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
},
|
||||
status: {
|
||||
type: DataTypes.STRING(32),
|
||||
allowNull: false,
|
||||
defaultValue: 'pending',
|
||||
},
|
||||
createdByUserId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: User,
|
||||
key: 'id',
|
||||
},
|
||||
onDelete: 'SET NULL',
|
||||
field: 'created_by_user_id',
|
||||
},
|
||||
acceptedByUserId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: User,
|
||||
key: 'id',
|
||||
},
|
||||
onDelete: 'SET NULL',
|
||||
field: 'accepted_by_user_id',
|
||||
},
|
||||
acceptedAt: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: true,
|
||||
field: 'accepted_at',
|
||||
},
|
||||
}, {
|
||||
tableName: 'friendly_match_invitation',
|
||||
underscored: true,
|
||||
timestamps: true,
|
||||
indexes: [
|
||||
{
|
||||
fields: ['to_club_id', 'status', 'proposed_date'],
|
||||
},
|
||||
{
|
||||
fields: ['from_club_id', 'status', 'proposed_date'],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
export default FriendlyMatchInvitation;
|
||||
186
backend/models/FriendlyMatchShared.js
Normal file
186
backend/models/FriendlyMatchShared.js
Normal file
@@ -0,0 +1,186 @@
|
||||
import { DataTypes } from 'sequelize';
|
||||
import sequelize from '../database.js';
|
||||
import Club from './Club.js';
|
||||
import User from './User.js';
|
||||
|
||||
const FriendlyMatchShared = sequelize.define('FriendlyMatchShared', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
allowNull: false,
|
||||
},
|
||||
homeClubId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: Club,
|
||||
key: 'id',
|
||||
},
|
||||
onDelete: 'CASCADE',
|
||||
field: 'home_club_id',
|
||||
},
|
||||
guestClubId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: Club,
|
||||
key: 'id',
|
||||
},
|
||||
onDelete: 'CASCADE',
|
||||
field: 'guest_club_id',
|
||||
},
|
||||
date: {
|
||||
type: DataTypes.DATEONLY,
|
||||
allowNull: false,
|
||||
},
|
||||
startTime: {
|
||||
type: DataTypes.TIME,
|
||||
allowNull: true,
|
||||
field: 'start_time',
|
||||
},
|
||||
matchName: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: true,
|
||||
field: 'match_name',
|
||||
},
|
||||
homeTeamName: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: false,
|
||||
field: 'home_team_name',
|
||||
},
|
||||
guestTeamName: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: false,
|
||||
field: 'guest_team_name',
|
||||
},
|
||||
locationName: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: true,
|
||||
field: 'location_name',
|
||||
},
|
||||
locationAddress: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: true,
|
||||
field: 'location_address',
|
||||
},
|
||||
locationCity: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: true,
|
||||
field: 'location_city',
|
||||
},
|
||||
locationZip: {
|
||||
type: DataTypes.STRING(32),
|
||||
allowNull: true,
|
||||
field: 'location_zip',
|
||||
},
|
||||
matchSystem: {
|
||||
type: DataTypes.STRING(120),
|
||||
allowNull: false,
|
||||
defaultValue: 'Braunschweiger System',
|
||||
field: 'match_system',
|
||||
},
|
||||
singlesCount: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 12,
|
||||
field: 'singles_count',
|
||||
},
|
||||
doublesCount: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 4,
|
||||
field: 'doubles_count',
|
||||
},
|
||||
winningSets: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 3,
|
||||
field: 'winning_sets',
|
||||
},
|
||||
homeMatchPoints: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
field: 'home_match_points',
|
||||
},
|
||||
guestMatchPoints: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
field: 'guest_match_points',
|
||||
},
|
||||
isCompleted: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: false,
|
||||
field: 'is_completed',
|
||||
},
|
||||
homeParticipants: {
|
||||
type: DataTypes.JSON,
|
||||
allowNull: true,
|
||||
field: 'home_participants',
|
||||
},
|
||||
guestParticipants: {
|
||||
type: DataTypes.JSON,
|
||||
allowNull: true,
|
||||
field: 'guest_participants',
|
||||
},
|
||||
resultDetails: {
|
||||
type: DataTypes.JSON,
|
||||
allowNull: true,
|
||||
field: 'result_details',
|
||||
},
|
||||
playersReady: {
|
||||
type: DataTypes.JSON,
|
||||
allowNull: true,
|
||||
field: 'players_ready',
|
||||
},
|
||||
playersPlanned: {
|
||||
type: DataTypes.JSON,
|
||||
allowNull: true,
|
||||
field: 'players_planned',
|
||||
},
|
||||
playersPlayed: {
|
||||
type: DataTypes.JSON,
|
||||
allowNull: true,
|
||||
field: 'players_played',
|
||||
},
|
||||
status: {
|
||||
type: DataTypes.STRING(32),
|
||||
allowNull: false,
|
||||
defaultValue: 'active',
|
||||
},
|
||||
createdByUserId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: User,
|
||||
key: 'id',
|
||||
},
|
||||
onDelete: 'SET NULL',
|
||||
field: 'created_by_user_id',
|
||||
},
|
||||
createdFromInvitationId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
field: 'created_from_invitation_id',
|
||||
},
|
||||
}, {
|
||||
tableName: 'friendly_match_shared',
|
||||
underscored: true,
|
||||
timestamps: true,
|
||||
indexes: [
|
||||
{
|
||||
fields: ['home_club_id', 'date', 'start_time'],
|
||||
},
|
||||
{
|
||||
fields: ['guest_club_id', 'date', 'start_time'],
|
||||
},
|
||||
{
|
||||
fields: ['status'],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
export default FriendlyMatchShared;
|
||||
@@ -58,6 +58,8 @@ import BillingDocument from './BillingDocument.js';
|
||||
import BillingDocumentValue from './BillingDocumentValue.js';
|
||||
import BillingUserSetting from './BillingUserSetting.js';
|
||||
import FriendlyMatch from './FriendlyMatch.js';
|
||||
import FriendlyMatchShared from './FriendlyMatchShared.js';
|
||||
import FriendlyMatchInvitation from './FriendlyMatchInvitation.js';
|
||||
import MemberTtrHistory from './MemberTtrHistory.js';
|
||||
import MemberPlayInterest from './MemberPlayInterest.js';
|
||||
import ClickTtAccount from './ClickTtAccount.js';
|
||||
@@ -410,6 +412,37 @@ ClubDisabledPresetGroup.belongsTo(Club, { foreignKey: 'clubId', as: 'club' });
|
||||
TrainingGroup.hasMany(TrainingTime, { foreignKey: 'trainingGroupId', as: 'trainingTimes' });
|
||||
TrainingTime.belongsTo(TrainingGroup, { foreignKey: 'trainingGroupId', as: 'trainingGroup' });
|
||||
|
||||
// Friendly shared matches
|
||||
FriendlyMatchShared.belongsTo(Club, { foreignKey: 'homeClubId', as: 'homeClub' });
|
||||
FriendlyMatchShared.belongsTo(Club, { foreignKey: 'guestClubId', as: 'guestClub' });
|
||||
Club.hasMany(FriendlyMatchShared, { foreignKey: 'homeClubId', as: 'homeSharedFriendlyMatches' });
|
||||
Club.hasMany(FriendlyMatchShared, { foreignKey: 'guestClubId', as: 'guestSharedFriendlyMatches' });
|
||||
|
||||
FriendlyMatchShared.belongsTo(User, { foreignKey: 'createdByUserId', as: 'createdByUser' });
|
||||
User.hasMany(FriendlyMatchShared, { foreignKey: 'createdByUserId', as: 'createdSharedFriendlyMatches' });
|
||||
|
||||
// Friendly invitations
|
||||
FriendlyMatchInvitation.belongsTo(Club, { foreignKey: 'fromClubId', as: 'fromClub' });
|
||||
FriendlyMatchInvitation.belongsTo(Club, { foreignKey: 'toClubId', as: 'toClub' });
|
||||
Club.hasMany(FriendlyMatchInvitation, { foreignKey: 'fromClubId', as: 'sentFriendlyMatchInvitations' });
|
||||
Club.hasMany(FriendlyMatchInvitation, { foreignKey: 'toClubId', as: 'receivedFriendlyMatchInvitations' });
|
||||
|
||||
FriendlyMatchInvitation.belongsTo(User, { foreignKey: 'createdByUserId', as: 'createdByUser' });
|
||||
FriendlyMatchInvitation.belongsTo(User, { foreignKey: 'acceptedByUserId', as: 'acceptedByUser' });
|
||||
User.hasMany(FriendlyMatchInvitation, { foreignKey: 'createdByUserId', as: 'createdFriendlyMatchInvitations' });
|
||||
User.hasMany(FriendlyMatchInvitation, { foreignKey: 'acceptedByUserId', as: 'acceptedFriendlyMatchInvitations' });
|
||||
|
||||
FriendlyMatchShared.belongsTo(FriendlyMatchInvitation, {
|
||||
foreignKey: 'createdFromInvitationId',
|
||||
as: 'sourceInvitation',
|
||||
constraints: false,
|
||||
});
|
||||
FriendlyMatchInvitation.hasOne(FriendlyMatchShared, {
|
||||
foreignKey: 'createdFromInvitationId',
|
||||
as: 'createdSharedMatch',
|
||||
constraints: false,
|
||||
});
|
||||
|
||||
export {
|
||||
User,
|
||||
Log,
|
||||
@@ -468,6 +501,8 @@ export {
|
||||
BillingDocumentValue,
|
||||
BillingUserSetting,
|
||||
FriendlyMatch,
|
||||
FriendlyMatchShared,
|
||||
FriendlyMatchInvitation,
|
||||
MemberTtrHistory,
|
||||
MemberPlayInterest,
|
||||
ClickTtAccount,
|
||||
|
||||
Reference in New Issue
Block a user