Skip to main content
The awards management system enables administrators to configure festival awards, assign them to winning films, and organize films into categories.

Award Data Model

Awards are simple entities that link to films:
// /back/src/models/Award.js:5-28
const Award = sequelize.define('Award', {
  id_award: {
    type: DataTypes.INTEGER,
    primaryKey: true,
    autoIncrement: true
  },
  id_movie: {
    type: DataTypes.INTEGER,
    allowNull: false
  },
  award_name: DataTypes.STRING(100)
}, {
  tableName: 'awards',
  timestamps: true
});

Award.associate = function(models) {
  Award.belongsTo(models.Movie, {
    foreignKey: 'id_movie'
  });
};
Each award is linked to a specific film. A film can receive multiple awards (e.g., “Best Film”, “Audience Choice”, “Technical Excellence”).

Award Types

Common festival award categories:
Primary Festival Recognitions
  • Best Film - Overall top film selection
  • Grand Prix - Jury’s highest honor
  • Best Director - Outstanding direction
  • Best Screenplay - Exceptional writing
  • Best Actor/Actress - Performance awards
These awards typically require candidate status and jury consensus.

Creating Awards

Admin Award Creation

Administrators create awards and assign them to films:
// POST /awards/:id_movie
async function createAward(req, res) {
  const { award_name } = req.body;
  const { id_movie } = req.params;

  if (!award_name) {
    return res.status(400).json({ 
      error: "Tous les champs doivent être remplis" 
    });
  }

  // Check for duplicate
  const existingAward = await Award.findOne({ 
    where: { award_name } 
  });
  
  if (existingAward) {
    return res.json({ 
      message: "Award déjà existant", 
      award: existingAward 
    });
  }

  // Create new award
  const newAward = await Award.create({
    award_name: award_name,
    id_movie: id_movie
  });
  
  return res.status(201).json({ 
    message: "Award créé", 
    newAward 
  });
}

Bulk Award Assignment

// Example: Assign multiple awards
const awards = [
  { award_name: "Best Film", id_movie: 42 },
  { award_name: "Best Director", id_movie: 42 },
  { award_name: "Technical Excellence", id_movie: 42 }
];

for (const award of awards) {
  await Award.create(award);
}

// Update film status
await Movie.update(
  { selection_status: "awarded" },
  { where: { id_movie: 42 } }
);
When a film receives its first award, its selection_status should be updated to “awarded” to reflect its winning status.

Retrieving Awards

Get All Awards

// GET /awards
function getAward(req, res) {
  Award.findAll().then((awards) => {
    res.json(awards);
  });
}

Get Award by ID

// GET /awards/:id
function getAwardById(req, res) {
  const { id } = req.params;

  Award.findOne({ where: { id_award: id } })
    .then((award) => {
      if (award) {
        res.json(award);
      } else {
        res.status(404).json({ error: "Award non trouvé" });
      }
    });
}

Get Awards for Film

// Get film with all its awards
const movie = await Movie.findByPk(id_movie, {
  include: [{
    model: Award,
    required: false
  }]
});

// Response format:
{
  "id_movie": 42,
  "title": "AI Dreams",
  "selection_status": "awarded",
  "Awards": [
    {
      "id_award": 1,
      "award_name": "Best Film",
      "createdAt": "2024-03-15T10:00:00Z"
    },
    {
      "id_award": 2,
      "award_name": "Best Director",
      "createdAt": "2024-03-15T10:05:00Z"
    }
  ]
}

Updating Awards

Modify Award Details

// PUT /awards/:id
function updateAward(req, res) {
  const { id } = req.params;
  const { award_name, id_movie } = req.body;

  Award.findOne({ where: { id_award: id } })
    .then((award) => {
      if (!award) {
        return res.status(404).json({ 
          error: "Award non trouvé" 
        });
      }

      // Update fields
      if (award_name) {
        award.award_name = award_name;
      }
      if (id_movie) {
        award.setDataValue("id_movie", Number(id_movie));
        award.changed("id_movie", true);
      }

      return award.save();
    })
    .then((updatedAward) => {
      res.json({ 
        message: "Award mis à jour", 
        updatedAward 
      });
    })
    .catch((err) => {
      res.status(500).json({ error: err.message });
    });
}

Reassign Award to Different Film

// Transfer award from one film to another
const award = await Award.findByPk(id_award);
await award.update({ id_movie: new_movie_id });

Deleting Awards

// DELETE /awards/:id
function deleteAward(req, res) {
  const { id } = req.params;
  
  Award.destroy({ where: { id_award: id } })
    .then(() => {
      return res.status(200).json({ 
        message: "Prix supprimé" 
      });
    });
}
Deleting an award does not automatically change the film’s status. If a film has no remaining awards, manually update its status from “awarded” to “candidate”.

Category Management

Category Model

Categories organize films by type:
// /back/src/models/Categorie.js:5-26
const Categorie = sequelize.define('Categorie', {
  id_categorie: {
    type: DataTypes.INTEGER,
    primaryKey: true,
    autoIncrement: true
  },
  name: DataTypes.STRING
}, {
  tableName: 'categories',
  timestamps: true
});

Categorie.associate = function(models) {
  Categorie.belongsToMany(models.Movie, {
    through: 'movies_categories',
    foreignKey: 'id_categorie',
    otherKey: 'id_movie'
  });
};

Many-to-Many Relationship

Films can belong to multiple categories:
// /back/src/models/Movie.js:134-138
Movie.belongsToMany(models.Categorie, {
  through: 'movies_categories',
  foreignKey: 'id_movie',
  otherKey: 'id_categorie'
});

Example Categories

  • Animation - Animated films
  • Documentary - Non-fiction films
  • Narrative - Story-driven fiction
  • Experimental - Avant-garde works
  • AI-Generated - Fully AI-created content
  • AI-Assisted - Hybrid human-AI production
  • Student Films - Educational projects
  • International - Non-local productions

Assigning Categories to Films

Single Film Category Update

// PUT /movies/:id/categories (ADMIN only)
async function updateMovieCategories(req, res) {
  const { id } = req.params;
  let { categories } = req.body;

  // Parse if JSON string
  if (typeof categories === "string") {
    try {
      categories = JSON.parse(categories);
    } catch (parseError) {
      categories = [];
    }
  }

  // Ensure array of integers
  const categoryIds = categories
    .map((value) => Number(value))
    .filter((value) => Number.isInteger(value));

  const movie = await Movie.findByPk(id);
  if (!movie) {
    return res.status(404).json({ error: "Film non trouvé" });
  }

  // Set categories (replaces existing)
  await movie.setCategories(categoryIds);

  // Fetch updated movie with categories
  const updatedMovie = await Movie.findByPk(id, {
    include: [
      { model: Categorie, through: { attributes: [] } }
    ]
  });

  res.json({ 
    message: "Catégories mises à jour", 
    movie: updatedMovie 
  });
}

Category Assignment During Submission

// During film creation
const newMovie = await Movie.create({
  title: "AI Dreams",
  description: "A film about...",
  id_user: userId
});

// Assign to categories
const categoryIds = [1, 3, 5]; // Animation, AI-Generated, International
await newMovie.setCategories(categoryIds);

Award Workflow Integration

1
Step 1: Jury Voting Complete
2
Films reach “candidate” status after positive jury consensus.
3
const candidates = await Movie.findAll({
  where: { selection_status: "candidate" },
  include: [
    { model: Vote },
    { model: User, as: "Producer" }
  ]
});
4
Step 2: Admin Review
5
Administrators review candidates and determine award recipients.
6
// Review vote consensus
const votes = await Vote.findAll({ 
  where: { id_movie: candidateId } 
});

const yesVotes = votes.filter(v => v.note === "YES").length;
const consensus = yesVotes / votes.length;

if (consensus >= 0.75) {
  // High consensus - strong candidate
}
7
Step 3: Award Assignment
8
Create awards for winning films.
9
// Assign Best Film award
const award = await Award.create({
  award_name: "Best Film",
  id_movie: winningFilmId
});

// Update film status
const movie = await Movie.findByPk(winningFilmId);
movie.selection_status = "awarded";
await movie.save();
10
Step 4: Notification
11
Notify producers of their awards.
12
import { sendTemplateEmail } from "./utils/mailer.js";

const movie = await Movie.findByPk(winningFilmId, {
  include: [{ model: User, as: "Producer" }]
});

await sendTemplateEmail({
  to: movie.Producer.email,
  templateId: process.env.BREVO_TEMPLATE_AWARD_NOTIFICATION,
  params: {
    first_name: movie.Producer.first_name,
    movie_title: movie.title,
    award_name: "Best Film"
  }
});

Dashboard Views

Admin Award Management

// Get all awarded films with their awards
const awardedFilms = await Movie.findAll({
  where: { selection_status: "awarded" },
  include: [
    {
      model: Award,
      required: true
    },
    {
      model: User,
      as: "Producer",
      attributes: ["first_name", "last_name", "email"]
    }
  ],
  order: [[Award, "createdAt", "DESC"]]
});

Public Award Display

// Get winners for public website
const winners = await Award.findAll({
  include: [{
    model: Movie,
    include: [
      { model: User, as: "Producer" },
      { model: Categorie }
    ]
  }],
  order: [["createdAt", "DESC"]]
});

// Group by award type
const groupedAwards = winners.reduce((acc, award) => {
  if (!acc[award.award_name]) {
    acc[award.award_name] = [];
  }
  acc[award.award_name].push(award.Movie);
  return acc;
}, {});

API Endpoints Summary

MethodEndpointRoleDescription
GET/awardsALLGet all awards
GET/awards/:idALLGet award by ID
POST/awards/:id_movieADMINCreate award for film
PUT/awards/:idADMINUpdate award
DELETE/awards/:idADMINDelete award
PUT/movies/:id/categoriesADMINAssign categories to film
GET/categoriesALLGet all categories
POST/categoriesADMINCreate new category

Frontend Integration Example

Award Assignment Interface

import { useMutation, useQuery } from "@tanstack/react-query";

function AwardAssignment({ movieId }) {
  const [awardName, setAwardName] = useState("");

  // Fetch existing awards
  const { data: movie } = useQuery({
    queryKey: ["movie", movieId],
    queryFn: () => fetchMovie(movieId)
  });

  // Create award mutation
  const createAwardMutation = useMutation({
    mutationFn: (data) => createAward(movieId, data),
    onSuccess: () => {
      alert("Prix attribué avec succès");
      setAwardName("");
    }
  });

  const handleSubmit = (e) => {
    e.preventDefault();
    createAwardMutation.mutate({ award_name: awardName });
  };

  return (
    <div>
      <h2>{movie?.title}</h2>
      
      <h3>Prix attribués:</h3>
      <ul>
        {movie?.Awards?.map(award => (
          <li key={award.id_award}>{award.award_name}</li>
        ))}
      </ul>

      <form onSubmit={handleSubmit}>
        <input
          type="text"
          value={awardName}
          onChange={(e) => setAwardName(e.target.value)}
          placeholder="Nom du prix"
          required
        />
        <button type="submit">Attribuer le prix</button>
      </form>
    </div>
  );
}

Category Filter

function CategoryFilter() {
  const [selectedCategory, setSelectedCategory] = useState(null);

  const { data: categories } = useQuery({
    queryKey: ["categories"],
    queryFn: fetchCategories
  });

  const { data: films } = useQuery({
    queryKey: ["films", selectedCategory],
    queryFn: () => fetchFilmsByCategory(selectedCategory)
  });

  return (
    <div>
      <select onChange={(e) => setSelectedCategory(e.target.value)}>
        <option value="">Toutes les catégories</option>
        {categories?.map(cat => (
          <option key={cat.id_categorie} value={cat.id_categorie}>
            {cat.name}
          </option>
        ))}
      </select>

      <div>
        {films?.map(film => (
          <FilmCard key={film.id_movie} film={film} />
        ))}
      </div>
    </div>
  );
}

Best Practices

  • Use clear, descriptive names
  • Follow festival tradition (e.g., “Grand Prix” vs “Best Film”)
  • Be consistent across years
  • Consider bilingual names for international festivals
  • Avoid overly long names (max 100 characters)
  • Create mutually exclusive categories when possible
  • Allow multiple categories per film for flexibility
  • Use categories to organize jury assignments
  • Consider category-specific awards
  • Review and update categories annually
  • Verify film is in “candidate” status
  • Review jury consensus before awarding
  • Document decision rationale
  • Update film status to “awarded”
  • Send notification to producer immediately
  • Prepare public announcement materials

Statistics & Reporting

Award Distribution

// Count awards per category
const awardStats = await Award.findAll({
  attributes: [
    'award_name',
    [sequelize.fn('COUNT', sequelize.col('id_award')), 'count']
  ],
  group: ['award_name']
});

Category Coverage

// Films per category
const categoryStats = await Categorie.findAll({
  attributes: [
    'name',
    [sequelize.fn('COUNT', sequelize.col('Movies.id_movie')), 'film_count']
  ],
  include: [{
    model: Movie,
    attributes: [],
    through: { attributes: [] }
  }],
  group: ['Categorie.id_categorie']
});

Export Award Data

// Generate award ceremony program
async function generateAwardProgram() {
  const awards = await Award.findAll({
    include: [{
      model: Movie,
      include: [
        { model: User, as: "Producer" },
        { model: Categorie }
      ]
    }],
    order: [["award_name", "ASC"]]
  });

  return awards.map(award => ({
    award: award.award_name,
    film: award.Movie.title,
    director: `${award.Movie.Producer.first_name} ${award.Movie.Producer.last_name}`,
    categories: award.Movie.Categories.map(c => c.name),
    duration: award.Movie.duration
  }));
}